This is good stuff. I've certainly felt the same way about FP at some points--for me, Clojure really illuminates why people thought OO was such a good thing 15 years ago. Let me know if you write that blog post.
On Saturday, June 9, 2012 7:47:47 PM UTC-7, Kurt Harriger wrote: > > > 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 clojure@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+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en