Tim Bray has put up an excellent set of theses on Clojure. I can agree with most of them, but wonder about the idea that "Lisp is a handicap".
I understand where Tim comes from, and I fully admit that even after playing with Lisp, Scheme and Clojure for quite some time, I'm still not sure whether this is really an issue. I would only feel qualified to really comment on it once I have used it on a real project with some complexity. But what I found interesting is the example Tim gives:
(apply merge-with + (pmap count-lines (partition-all *batch-size* (line-seq (reader filename)))))
I find this particular example to be extremely readable if you read it from the inside out - the reader
function presumably returns a reader for the file named filename
; line-seq
returns a (lazy) sequence of the lines in the file, partition-all
cuts this into segments (using *batch-size*
as the, well, batch size), pmap
maps the function count-lines
over the result in parallel, returning a list of maps; finally, merge-with +
combines all of the values for the same keys in the map by adding them. (Obviously the source code is much more understandable than my prose version.)
I agree there is a ton of Lisp code out there that's intimidating, but I don't see this as a good example. Let's invent a language with similar expressiveness, but more traditional syntax:
apply(mergeWith(plus), pmap(countLines, partitionAll(BATCH_SIZE, lineSeq(reader(filename)))));
I can't see how this would be easier to read. It's not the syntax (at least not in this case) that might be a problem if you're not used to it, it's the style of using nested function invocations – which I believe is something you get used to really quickly if using any kind of language that supports functional idioms.
I’d say the real problem with both of those versions is that they implement pretty much the entire wide-finder project in one line of code. Breaking it up makes it much more readable (and puts it mostly back in the right order).
var lines = lineSeq(reader(filename)); var processedData = pmap(countLines, partitionAll(BATCH_SIZE, lines)); var result = mergeWith(plus, processedData);
You can vary how much or how little you break it down to get the balance right, but this is dramatically more readable than the original because you can see the three key components - get the data and split it into lines, process the data in parallel and finally put it all back together. Extracting some methods for those three lines to give them more readable names may be well worth while too.
Cheers,
Adrian Sutton.