Clojure objects and higher-order functions for encapsulation
This is a follow-up to my earlier question.
I came up with a bizarre object scheme from my reading of Let Over Lambda and can think of no advantages over protocols, but want to get opinions. I am just exploring the use of higher-order functions and encapsulation.
(defn new-person
"Construct a new Person object and re开发者_开发问答turn a Map of accessor methods."
[init-first-name init-last-name init-age]
(let [first-name (ref init-first-name)
last-name (ref init-last-name)
age (ref init-age)]
{:get-first-name #(@first-name)
:set-first-name #(dosync (ref-set first-name %1))
:get-last-name #(@last-name)
:set-last-name #(dosync (ref-set last-name %1))
:get-age #(@age)
:set-age #(dosync (ref-set age %1))}))
I can use the object like this:
(def fred (new-person "fred" "flintstone" 42))
and retrieve an accessor method this way:
(fred :get-age)
but I can't figure out how to call the accessor.
The object created is thread-safe since all mutation of "instance" variables occurs in the STM.
UPDATE: New and improved version:
(defn new-person
"Construct a new Person object and return a Map of accessor methods."
[init-first-name init-last-name init-age]
(let [first-name (ref init-first-name)
last-name (ref init-last-name)
age (ref init-age)]
{:first-name
(fn
([] @first-name)
([val] (dosync (ref-set first-name val))))
:last-name
(fn
([] @last-name)
([val] (dosync (ref-set last-name val))))
:age
(fn
([] @age)
([val] (dosync (ref-set age val))))}))
Maybe not a 100 % answer to your question, but would you try to do is not very idiomatic for Clojure. The 'standard' solution would be something like:
(defrecord Person [first-name last-name age])
(def fred (Person. "fred" "flintstone" 42))
(fred :age)
It looks like you are forcing OO mutable state into Clojure 'objects'
Its the same as your follow up question, wrap the form in another set of parenthesis. When an inner form returns a function, its the same as a symbol that returns a function. The rule is the first form in a parenthesis will always be looked up as a special form, macro, or function. You need something like quote to prevent this behavior.
user=> (fred :get-age)
#<user$new_person$fn__531 user$new_person$fn__531@c4afc4>
user=> ((fred :get-age))
42
Its the same as
user=> (let [age-getter (fred :get-age)] (age-getter))
42
Clojure philosophy is NOT to encapsulate access to the record fields themselves. Encapsulation should happen on higher levels, for example in the set of functions that work with that records. See Clojure - datatypes:
Encapsulation of information is folly fields are public, use protocols/interfaces to avoid dependencies
精彩评论