Clojure Tradeoffs
gtrak.wordpress.com
gtrak.wordpress.com
This article is a paean to Clojure, a torrent of praise with little to no (or very faint) criticism, and has very little discussion of what is being traded off for what, and what the positives and negatives are, what the benefits and costs are, of each tradeoff.
The "Tradeoffs for Individuals" paragraph is a good example. Literally every sentence of that paragraph is a variant of "Clojure is cool". There are no 'tradeoffs for individuals' actually discussed in that paragraph, inspite of the title.
This is not to say Clojure isn't cool. It is. Rich Hickey has a great sense of design and is a brilliant engineer. But an article titled "Clojure Tradeoffs" should actually discuss, you know, tradeoffs.
Misleading title, but otherwise a decent article. A good alternative title would be "Why I Think Clojure is a Great Language"
Also, the STM has an interesting intersection with garbage collection depending on your access patterns. Returning modified copies & throwing away references to the old one, rather than mutating, can increase pressure on the GC since you end up with more transient objects. Especially if you're "mutating" objects that all end up living in the tenured generation.
As far as Clojure iteration is concerned, Vectors (immutable, persistent) are actually faster than ArrayLists (mutable, unsynchronized). Criterium reports:
(let [v (vec (range 10000))] (bench (reduce + v)))
Evaluation count : 95340 in 60 samples of 1589 calls.
Execution time mean : 642.508722 µs
Execution time std-deviation : 5.452816 µs
Execution time lower quantile : 636.188082 µs ( 2.5%)
Execution time upper quantile : 658.023220 µs (97.5%)
(let [a (java.util.ArrayList. (range 10000))]
(bench (reduce + a)))
Evaluation count : 77640 in 60 samples of 1294 calls.
Execution time mean : 739.523157 µs
Execution time std-deviation : 6.064187 µs
Execution time lower quantile : 730.818794 µs ( 2.5%)
Execution time upper quantile : 751.968107 µs (97.5%)
Amusingly, using iterators directly instead of reduce speeds up ArrayLists (since there's no need to go through seq, I think), and slows down Vectors to the same speed: (defn iterator-sum
[^java.util.Iterator i]
(loop [sum 0]
(if (.hasNext i)
(recur (+ sum (.next i)))
sum)))
(defn iterable-sum
[^Iterable i]
(iterator-sum (.iterator i)))
(let [v (vec (range 10000))]
(bench (iterable-sum v))))
Execution time mean : 682.147166 µs
(let [a (java.util.ArrayList. (range 10000))]
(bench (iterable-sum a))))
Execution time mean : 683.351808 µs
After conferring with ztellman, I suspect this is due to Clojure's InternalReduce avoiding extra iterator allocations over vectors, since it can recur f directly over the internal array at each leaf node.http://clojure.com/blog/2012/05/08/reducers-a-library-and-mo...
EDIT: and my response is just a little redundant now
It was a pretty transparent change.
Lots of algorithms don't work with this kind of copying semantics, but you have all of Purely Functional Data Structures [1] to help.
[1] http://www.amazon.com/Purely-Functional-Structures-Chris-Oka...
Efficient immmutable data structures are easily one of Clojure best features. Easiest trade off ever (though the actual hit to performance isn't that bad) imo.
See for instance, the "some" implementation: https://github.com/clojure/clojure/blob/c6756a8bab137128c811...
Pretty sure you mean "mutation", because none of your criticisms makes sense for iteration of persistent datastructures in general (the former one makes no sense at all, the latter one may make sense in tree-based persistent datastructures which will have higher constant factors due to pointer chasing)
When I read the title here on HN, I anticipated a balanced and comprehensive discussion on the design tradeoffs and was (mildly) disappointed to see that the article seemed to have a different focus (making a case for why Clojure is a language worth investing in, as far as I can make out).
I think Clojure is a brilliant language, but don't have enough experience with it to write about tradeoffs, and would really like to see a good discussion.
As I said in my original comment, it is a decent blog entry. My criticism is only about the (imo) mismatch between title and content.
All essays about Clojure must either be a narrative in which "a man goes on a journey" or one in which "a stranger comes to town." [1] Yours, being the first type is just an old-fashioned love song to Lisp. [2] The other narrative form produces rants about s-expression syntax - though now that we have Python, praise of the semicolon is no longer a given.[3]
[1] https://news.ycombinator.com/item?id=1213770
[2] e.g. RPG's www.dreamsongs.com
[3] Such essays, lacking condescension, may be said to fail to comprehend the essence of Lisp.
I think if you're mutating deeply nested things, or doing heavy algorithmic bookkeeping, it's worthwhile to evaluate different approaches (maybe pure java) as an 80/20 thing, and I wonder how hard it would be to have comparable mutability facilities and data structures as a library?
Of course, programmers want to get things done without spending time on building up their own tools, and I understand that.