Ask HN: How dangerous is Clojure's immutability assumption?
I'm a long-time python programmer, thinking of learning clojure because I want to play with my droid, and java drives me nuts. I've been reading Learning Clojure[1], where it talks about data immutability as a fundamental assumption of Clojure's model. It says "Watch out for cases of mutable Java objects stored in immutable Clojure collection objects. If the mutable object changes, this won't be reflected in the collection's hash." This sounds potentially horrendously difficult to debug.
I don't want to start a language war, here. I'd be interested to hear people's personal experience with this issue, and also of any tools or language features which mitigate this issue, either by making it a very unlikely mistake, or making such cryptic mutability easy to detect.
[1] http://en.wikibooks.org/wiki/Learning_Clojure
10 comments
[ 5.4 ms ] story [ 36.0 ms ] threadI would not consider this "dangerous". It's a side condition you'll need to check. The language can make this more or less easy to establish.
Typically it looks like your value is changing under the hood. It's relatively easy to debug -- since the result is so unexpected.
It's rare in Haskell. I imagine it is a bit more common in Clojure, where they rely more on Java code than Haskell does on C code.
I don't believe Clojure is an optimizing compiler -- it's not doing any optimizations based on static guarantees of referential transparency -- so that simplifies the issue. If the compiler can't guarantee purity, there's less it can do with to your code to take advantage of that, so less unusual semantics.
I've been using Clojure for a year and half and never run into such a bug. You learn quickly that you lost all of the benefits of persistent datastructures if you're putting mutable objects into them.
As a side note, Rich Hickey has been working on a project called "cells" which allows you to use even unsafe mutable Java objects with the same safe concurrency guarantees as Clojure persistent data structures.
It takes some debugging to find this is happening, but in five years of writing Java and Python, I've only had it happen once in either language.
You could enable something like that during the debugging phase of your development to get 'peace of mind' that such behaviour is not the source of any bugs.
The upshot of this is that if you're using a Java library, you'll generally want to either:
(a) build a clojure wrapper API so as to enforce some sane semantics on it (see clojure.contrib.http.agent for a good example of this, where HTTP interactions are wrapped in clojure agents and a good set of convenience functions that make working with the JDK's HttpURLConnection and friends way more pleasant than usual).
(b) confine the usage of key Java libraries in such a way that there's a clear line of demarcation between clojure's mutability and concurrency semantics and the free-for-all in the rest of Java. This is where the big win is in programming Swing interfaces, for example, where your core data model would ideally be implemented using persistent data structures and clojure's reference objects to ensure sane concurrency semantics, and you take all the usual precautions when touching the Swing APIs.
Then it's probably not a good idea to use the word "horrendous". Your commentary is not what would start a language war; your tone is.
For example, one list isn't equal to another list, even if it has the same elements, if modifying one list doesn't modify the other. If they're not the same, then they shouldn't compare as equal.
This is one reason I think Java's implementation of hashCode() on collection classes isn't very smart. I think .NET gets it right, having GetHashCode() return a consistent value for mutable collections. (Similar comments apply to the corresponding equality operation.)
But mutable objects passed around by value are bad for other reasons, such as the risk of modifying copies when you think you're modifying an underlying value.