> For example, if I changed the age property to be the result of a function,
> I could either replace the value of age with a function that calculates age
> or write a function(person)->age.
> Both of those are disruptive to the consumers of person.
> I understand that clojure is about explicitly distinguishing between state
> and functions, but I see this as a high price to pay. Have I missed
> something? The OO in me is saying "well, never introspect a map directly,
> rather provide get-X(person) functions" but that is very very noisy.
But that's more or less what you'd have to do. If age might be
calculated in some more complex manner, now or in the future, you want
something like
(defn age [person]
(:age person))
or whatever.
--
Protege: What is this seething mass of parentheses?!
Master: Your father's Lisp REPL. This is the language of a true
hacker. Not as clumsy or random as C++; a language for a more
civilized age.
and just today this was posted to reddit: http://skillsmatter.com/podcast/scala/talk-by-patrick-fredriksson
Sincerely
Meikel
I must admit my thoughts are still not fixed concerning this, guided
by several considerations:
* consider the ring spec: it specifies what keys are expected to be
present in the request map, the response map. Good enough.
* Wait ! what if some keys could be calculated from others (derived)
=> they may not be essential, put them in a function.
* Wait ! what if the computation of the value of the derived key heavyweight ?
* answer 1 : let the consumer "cache" it.
* answer 2 : "cache" the value in the map, but make it clear that
it costs something to compute it, e.g. by placing it explicitly (as in
"in the spec of your map") in a `delay` construct
* choice between 1 and 2 will obviously be in the library's designer hand
* I tend to be very liberal with the use of maps inside the library
I'm writing : after all, no other code than my library will depend on
it, so I assume the choice of breaking inner parts of my lib by
exposing map keys everywhere in it. It's a kind of cost/benefit
tradeoff: no cost upfront, many benefits, and if later I have to
change more things in my lib than if I had encapsulated things, then I
both grumble and then think that the "price" of the change has been
paid several times by not having paid the cost of having encapsulated
all parts of my lib "concepts".
* I tend to be more selective with the parts of the lib which are
exposed to consumers.
* All in all, it may not be such a big deal, because of the
following characteristic of clojure: it emphazises representing in
maps only the "essential" pieces of your domain model. Those which
will be subject to change for "good reasons" (change in spec), not
"wrong reasons" (hopefully). And, also, when it's possible, I try to
only expose as maps to consumers as little as possible. The more
"objects" they retrieve from libraries are "opaque objects" (only
intended to be passed back to library functions), the better. This is
related to the previous point: the only "objects" which remain
"transparent" are then the maps which represent the "essential" part
of the concept (only data which are not computable from other data).
HTH,
--
Laurent
2011/6/15 Colin Yates <colin...@gmail.com>:
> --
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to clo...@googlegroups.com
> Note that posts from new members are moderated - please be patient with your
> first post.
> To unsubscribe from this group, send email to
> clojure+u...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en
On Wed, Jun 15, 2011 at 11:41 AM, Colin Yates <colin...@gmail.com> wrote:
> the very common Person class will expose get/setName(), get/setAge() etc.
> and as a consumer I have no idea how the results are calcualted.
The FP approach certainly takes some getting used to after a lot of
Java! I like the fact that a lot of (post-Java) languages go out of
their way to avoid all the boilerplate get/set methods in various ways
because this is very high ceremony.
I was lucky enough to be exposed to functional programming in the 80's
and then move into OOP in the 90's (with C++ in '92 and Java in '97).
Whilst I am still "unlearning" some OOP habits, I'm more comfortable
with the non-OO approach and dusting off my older programming
approaches. I'm finding, as I introduce more people to Clojure, that
folks without a lot of OOP experience tend to pick up FP much quicker
and aren't as concerned about get/set "encapsulation".
My experience with OOP has been that essential properties of objects
rarely change into computed functions (and derived properties are
computed functions in the first place). That said, of course there are
situations where an essential property needs a function: when you have
side-effects on the get/set operation, such as recording changes, but
I'd argue that is likely to be known upfront. Also given the
preference for immutable data, you're much less likely to use/need
setters.
My suggestion would be to code with raw maps instead of
functions-wrapping-maps and see how things go. By using raw maps
you'll find that a lot of power can be brought to bear with standard
Clojure functions.
As for the age property on Person, that seems to be a common example
given to justify the use of getters but age is a derived property,
based on date of birth, and every real world system I've worked with
that represents people, and needs age, uses a function for it upfront.
So I think that's a bit of a strawman :)
Hope that helps?
--
Sean A Corfield -- (904) 302-SEAN
An Architect's View -- http://corfield.org/
World Singles, LLC. -- http://worldsingles.com/
Railo Technologies, Inc. -- http://www.getrailo.com/
"Perfection is the enemy of the good."
-- Gustave Flaubert, French realist novelist (1821-1880)
You're applying the function immediately. It's not different than doing
(let [age ((fn [] (- 2011 1979)))]
{:age age})
If you can't refactor the class, that's a relatively simple one to
solve with helper methods/functions:
(defn word-seq [s]
(map (partial apply str) (take-nth 2 (partition-by
#(Character/isWhitespace %) s))))
(defn is-not-capitalized [w]
(Character/isLowerCase (first w)))
(defn get-name [first last]
(str first " " last))
(defn get-first-name [full]
(first (word-seq full)))
(defn get-last-name [full]
(let [chunks (reverse (drop 1 (word-seq full)))]
(apply str
(interpose " "
(reverse
(cons
(first chunks)
(take-while is-not-capitalized (rest chunks))))))))
=> (get-name "Bob" "Marley")
"Bob Marley"
=> (get-first-name "Bob Marley")
"Bob"
=> (get-last-name "Bob Marley")
"Marley"
=> (get-last-name "Sarah Michelle Gellar")
"Gellar"
=> (get-last-name "Frederique van der Wal")
"van der Wal"
=> (get-last-name "Olivia d'Abo")
"d'Abo"
The only slightly tricky one is get-last-name, since some last names
are more than one word and these need to be distinguished from middle
names. Fortunately, these last names only seem to capitalize the last
chunk. The last three examples use three famous actresses' names to
demonstrate that it drops middle names, keeps multi-part surnames, and
keeps surnames that start with a noncapitalized letter but are only
one part.
Of course there may be some more obscure corner case that isn't
covered, and if the input is incorrectly capitalized or spaces are
missing, all bets are off.
Names with only one chunk are presumed to be first names, so
get-first-name returns the whole input and get-last-name an empty
string in those cases.
Note how the above is clean sequence-manipulation code without a
single ugly regexp in sight.
--
Some people, when confronted with a problem, think “I know, I'll use
regular expressions.”
Now they have two problems.
- Jamie Zawinski