« Introducing Cascalog: a Clojure-based query language for Hadoop | Main | Migrating data from a SQL database to Hadoop »
Saturday
Apr102010

Fun with equality in Clojure

I ran into some very non-intuitive behavior from Clojure recently. See if you can guess what "foo" is in the following examples:

Example 1:

user=> foo
1
user=> (= foo 1)
true
user=> (= [foo 2] [1 2])
true
user=> (= {foo 2} {1 2})
false

Example 2:

user=> foo
false
user=> (= foo false)
true
user=> (when foo (println "shouldn't print?"))
shouldn't print?
nil

Yikes, huh? Here are the answers:

Example 1: (def foo (Long. "1"))

Example 2: (def foo (Boolean. false))

For example 1, the map equality breaks down because Long and Integer have different hashcodes for the same numeric value. In example 2, Clojure considers anything besides false or nil to be true in a conditional, so that means a false Boolean object will be true in a conditional even though it's equal to "false".

I would definitely consider #1 a bug, as part of the contract of equality is that two equal objects have the same hashcode. #2 is more debatable, but it seems more intuitive that the Boolean object false be considered false in conditionals as well.

You should follow me on Twitter here.

Reader Comments (5)

Hi Nathan,

#1 is irritating, but it represents a deliberate compromise between sensible equality semantics (Clojure) and playing nice with the host (JVM) approach to maps and sets. It's not a bug, as the documentation for Clojure maps is specific on the point: "Hash maps require keys that correctly support hashCode and equals."

WRT #2, Clojure uses a single false because there is only one false, and to speed comparisons. In the example you are creating a second false. The notion that there is more than one false doesn't make sense! Java lets you say it, but in Clojure you shouldn't. Notice even your own language suggests this: you say "the boolean false" but you have really created "*a* boolean false".

April 10, 2010 | Unregistered CommenterStuart Halloway

That all makes sense, thanks.

The fact that (= (Boolean. false) false) is true, yet "false" and "(Boolean. false)" have different semantics w.r.t. conditionals still bothers me though. Seems like either (Boolean. false) should be false in conditionals or (Boolean. false) should not equal "false".

April 11, 2010 | Unregistered Commenternathanmarz

The use of that Boolean constructor is plainly discouraged in its documentation:

http://java.sun.com/javase/6/docs/api/java/lang/Boolean.html#Boolean%28boolean" rel="nofollow">http://java.sun.com/javase/6/docs/api/java/lang...)

Would you really suggest that all conditionals be made slower in order to accommodate its use?

April 11, 2010 | Unregistered CommenterRich Hickey

You're right, it's a good tradeoff. Playing around I found that (Boolean/valueOf false) behaves different in conditionals than (Boolean. false), so this is really an edge case.

Thanks Rich for the help and all the incredible work you've done on Clojure!

April 11, 2010 | Unregistered Commenternathanmarz

I wonder if there's even a good reason why Java provides that constructor at all?

April 14, 2010 | Unregistered CommenterWarren Wood

PostPost a New Comment

Enter your information below to add a new comment.
Author Email (optional):
Author URL (optional):
Post:
 
Some HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>