You could also (dorun (map f coll))
It is actually interesting you brought this up as I was recently
contrasting the OO principle "tell don't ask" with the functional way of
doing things. In OO a void method taking a visitor is preferred over
return values. One could perhaps say that (dorun (map f (generator))) is
pretty much the same thing as (each (generator) f) except that generator is
now a pure side effect free function where as visitors generally speaking
have side effects, although this isn't required for example (each coll
identity) is just as easy to use and test.
The more I thought about it the more I began to realize that the OO visitor
approach may still have some advantages over the functional approach. In a
typed language the generator can overload based on the visitor's type
signature, this makes refactoring and evolving code easier while still
preserving backward compatibility as different versions of a visitor might
have different method parameter types. Of course you could do this in
clojure explicitly but is less elegant ie (map f (generator
:non-default-type)) and is generally considered confusing to have functions
return different return types depending on their parameters. It is probably
better to just choose a different function name instead.
One other difference that is not as easy to emulate functionally is when
the generator spawns multiple threads and delegates the work where each
worker may call the visitor back on different threads. In clojure you can
easily (pmap f (generator)), however this requires generator to create a
sequence of return values from a specific thread regardless of how many
threads are used to generate the sequence or process the sequence,
ultimately the generator must first bring all these results together on a
single thread creating a synchronization bottleneck that does not
necessarily need to exist when using side effects instead of return values.
For example, the side effect might be to write its results to a database
and the generator might partition the work to each visitor never expecting
a reply from any of them, the visitor might even forward the request to a
different machine on the network since no return value is expected or
required.
Many will say that side-effecting functions are more difficult to test then
pure functions... However after writing about 4000 lines of clojure code, I
realized that things in practice are never quite as simple as they seem.
As functions are composed the data structures they work with grow larger
and more complex and this leads to maps containing maps containing lists
containing maps and a minor change downstream can ripple through the
program. Tests become significantly more complex and fragile as the input
and output structures grow in complexity.
This reminded me of another OO code smell.... "Don't talk to strangers" and
the Law of Demeter, instead sending and returning maps of lists of maps I
started returning maps of functions. This provided additional decoupling
that enabled me to refactor a bit more easily additionally maps of maps of
lists of maps often need to be fully computed where as a map containing
functions allows me to defer computation until it is actually required
which may in many cases be never. Although very idiomatic to use keywords
to get from maps, I have started to think of this as a code smell and
instead prefer to (def value :value) and use this var instead of the
keyword because it allows me to later replace the implementation or rename
properties if it is necessary to refactor and I want to minimize changes to
existing code or make changes to the existing code in small incremental
units rather than all at once.
It is also very hard to write a useful program that does not contain
side-effects or reads data from an impure datasource and I used midje to
mock functions that such as (get-current-time), many times my function did
not use that function directly but was used by another function that my
function called. I began to see this as a code smell in OO I would mock
the object that uses get-current-time not get-current-time itself which
then gave me the idea to start passing the functions used by the function
under test to the function as additional parameters, for testing I could
pass in an alternative function such as (constantly true) or (identity).
However, some functions required many functions so I began to group these
into maps and began to realize that I was reinventing OO programming...
perhaps there is a good reason lisp programmers invented CLOS in the first
place.
I'm starting to go off topic, and don't mean to troll, but your question as
innocent as it seems is perhaps one of the most important differences
between OO and functional programming ("Tell don't ask"). Perhaps the
functional solution is monads... but most find these confusing for some
reason and are hardly idiomatic. I digress... perhaps I will write a blog
post about the many other patterns and anti-patterns I discovered after
working with clojure for a bit.
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to [email protected]
Note that posts from new members are moderated - please be patient with your
first post.
To unsubscribe from this group, send email to
[email protected]
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en