Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it expected that identity returns something different from its argument?

Here is an example where calling identity changes the returned value, which seems to me to indicate that the docstring "Returns its argument." isn't entirely true:

(let [x Double/NaN] (identical? x x)) ;=> false
(let [x (identity Double/NaN)] (identical? x x)) ;=> true

Is this expected? Or is it a bug with the identity function somehow?

like image 894
Sam Estep Avatar asked Jun 27 '17 19:06

Sam Estep


2 Answers

You appear to have found an edge case involving identity, identical?, and primitive vs object equality. Note that in Java, java.lang.Double/NaN is a primitive:

public static final double NaN

But identical compares Java Objects:

; clojure.core
(defn identical?
  "Tests if 2 arguments are the same object"
  {:inline (fn [x y] `(. clojure.lang.Util identical ~x ~y))
   :inline-arities #{2}
   :added "1.0"}
  ([x y] (clojure.lang.Util/identical x y)))

// clojure/lang/Util.java
static public boolean identical(Object k1, Object k2){
    return k1 == k2;
}

Try this trick to force the NaN into a Double object instead of an unboxed primitive:

tupelo.core=> (let [x (Double. Double/NaN)] 
  (spyxx x) 
  (identical? x x))

x => java.lang.Double->NaN
true

I suspect that autoboxing of the primitive NaN which may/may not occur with different use-cases is the cause of the differences you are seeing.

like image 129
Alan Thompson Avatar answered Oct 13 '22 09:10

Alan Thompson


To add a little color to Alan's answer on boxing:

You may want to look into the == function, which is implemented this way:

public boolean equiv(Number x, Number y){
    return x.doubleValue() == y.doubleValue();
}

This performs a primitive comparison of two actual doubles. Your example, with ==:

(let [x (identity Double/NaN)] (== x x))
=> false
(let [x (identity Double/POSITIVE_INFINITY)] (== x x))
=> true

What's going on? Why is NaN == NaN false? Well, a primitive comparison using == should actually return false for NaN. It's strangely specified this way in IEEE 754 and Java behaves this way. It's the only "number" which, when compared to itself, does not equal itself.

As an aside, to see how object equality can be a strange thing in Java, see this:

(identical? 127 127)
=> true
(identical? 128 128)
=> false

This is because java caches the first 2^8 unsigned ints, so the 127s being compared are the same object in the first example, but the 128s in the second example are different objects. So, there are some gotchas to be aware of with checking for equality!

But the main takeaway here is: identity is working as it should! Just be careful when comparing things, as the notion of "equality" is not so straightforward!

like image 43
Josh Avatar answered Oct 13 '22 09:10

Josh