Created
July 6, 2012 19:01
-
-
Save marick/3062098 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;; Is it functional programming's immutability that makes it a bad match for | |
;; object-oriented style, particularly inheritance? | |
;; For /Functional Programming for the Object-Oriented Programmer/ | |
;; (http://leanpub.com/fp-oo), I've implemented something like the | |
;; Ruby object system in Clojure. Objects are built out of maps | |
;; (hashes) with stylized "metadata". Here's a `Point` object: | |
{:y 6, :x 4, :__class_symbol__ 'Point} | |
;; Classes are also objects, but they have additional metadata, such as | |
;; their superclass and a map of methods that apply to their instances. | |
;; Sending a message looks like this: | |
user=> (send-to Point :new 1 2) | |
{:y 2, :x 1, :__class_symbol__ Point} | |
user=> (def point (send-to Point :new 1 2)) | |
user=> (send-to point :shift 1 2) | |
{:y 4, :x 2, :__class_symbol__ Point} | |
;; Because Clojure has immutable maps, `:shift` produces a new `Point`. | |
;; Here's one implementation of `:shift`. It's a fairly typical OO | |
;; one, I think. Notice, for example, how it takes care to send | |
;; `:new` to the right class, which may be a subclass, rather than | |
;; hardcoding `Point`. | |
;; | |
:shift | |
(fn [this xinc yinc] | |
(let [my-class (send-to this :class)] | |
(send-to my-class :new | |
(+ (:x this) xinc) | |
(+ (:y this) yinc)))) | |
;; I use "direct instance variable access" rather than getters mainly because | |
;; `(send-to this :x)` is too annoying. Except for that, this code sticks closely | |
;; to the illusion that we have conventional objects, rather than maps. | |
;; But suppose we acknowledged the reality of maps. In Clojure, you merge | |
;; two maps with `merge`. There's a related function, `merge-with` that takes | |
;; an additional argument: the function to be applied when the two maps have | |
;; conflicting keys. Using that, we can write a much simpler `shift`: | |
:shift | |
(fn [this xinc yinc] | |
(merge-with + this {:x xinc, :y yinc})) | |
;; This is better. But there's more. Consider a subclass of `Point` called | |
;; `ColoredPoint`. It is constructed with an extra argument: the color of the point. | |
;; Like this: | |
(send-to ColoredPoint :new 1 2 'red) | |
;; It seems to make sense to shift colored points, but the superclass method | |
;; doesn't work in the subclass (because `:shift` only calls `:new` with two | |
;; arguments). We could write a version of new in the subclass: | |
:shift | |
(fn [this xinc yinc] | |
(let [my-class (send-to this :class)] | |
(send-to my-class :new | |
(+ (:x this) xinc) | |
(+ (:y this) yinc) | |
(:color this)))) | |
;; But this duplication seems wrong. We should use the superclass. But | |
;; doing that is annoyingly cumbersome. We might make a point, | |
;; populating it correctly, shifting it, then using the resulting Point's | |
;; x and y values to make a ColoredPoint. Blech. | |
;; Now note that the hash-aware version of `:shift` works perfectly | |
;; fine in ColoredPoint: no need for overriding it in the subclass. | |
;; This seems like an argument in favor of using maps instead of objects: additional | |
;; information is easy to work with. | |
;; You might object that this is a side-effect of Clojure's immutable maps. In Ruby, | |
;; `shift` would probably mutate the point: | |
class ColoredPoint < Point | |
def shift(xinc, yinc) | |
@x += xinc | |
@y += yinc | |
end | |
end | |
;; In that case, `shift` would work just fine in subclasses. | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment