好吧,我对identical?
的心理模型不准确。它调用clojure.lang.Util/identical
,其实现如下
static public boolean identical(Object k1, Object k2){
return k1 == k2;
}
当Clojure调用具有此类签名的函数时,它必须将参数中的原始值装箱。Double通过调用Double.valueOf(double)
进行装箱。NaN的不寻常等式语义使示例令人困惑,但在每种情况下,identical?
比较的是装箱值的引用等式,而不是数值等式。
通过查看反汇编的字节码(见下文),我们可以了解正在发生的事情。以下是总结。
- 在例子1、2和4中,
identical?
的每个参数都分别装箱。
- 在例3中,
##NaN
仅装箱一次,相同的装箱值用作identical?
的两个参数。
这里有一些更多的例子。请注意,##Inf
的行为与##NaN
是相同的。
(identical? ##Inf ##Inf) ; true
(let [x ##Inf] (identical? x x)) ; false
(identical? 1.0 1.0) ; false
(let [x 1.0] (identical? x x)) ; false
(def my-one 1.0)
(identical? my-one my-one) ; true
结论
一切运行正常。我对identical?
的 mental model 是错误的。它更像是(Object)... == (Object) ...
而不是简单的==
。
得到的教训:仅在使用identical?
时考虑引用等价性。
拆解示例
代码已经使用clj-java-decompiler进行了反编译。
示例 1 (identical? Double/NaN Double/NaN)
class user$fn_line_1__254
Minor version: 0
Major version: 52
Flags: PUBLIC, FINAL, SUPER
public void <init>();
Flags: PUBLIC
Code:
linenumber 1
0: aload_0
1: invokespecial clojure/lang/AFunction.<init>:()V
4: return
public static java.lang.Object invokeStatic();
Flags: PUBLIC, STATIC
Code:
linenumber 1
0: getstatic java/lang/Double.NaN:D
3: invokestatic java/lang/Double.valueOf:(D)Ljava/lang/Double;
linenumber 1
6: getstatic java/lang/Double.NaN:D
9: invokestatic java/lang/Double.valueOf:(D)Ljava/lang/Double;
linenumber 1
12: invokestatic clojure/lang/Util.identical:(Ljava/lang/Object;Ljava/lang/Object;)Z
15: ifeq 24
18: getstatic java/lang/Boolean.TRUE:Ljava/lang/Boolean;
21: goto 27
24: getstatic java/lang/Boolean.FALSE:Ljava/lang/Boolean;
27: areturn
StackMapTable: 00 02 18 42 07 00 1D
public java.lang.Object invoke();
Flags: PUBLIC
Code:
linenumber 1
0: invokestatic user$fn_line_1__254.invokeStatic:()Ljava/lang/Object;
3: areturn
static {};
Flags: PUBLIC, STATIC
Code:
linenumber 1
0: return
示例 2 (let [x Double/NaN] (identical? x x))
跳过;与示例 4 类似。
示例 3 (identical? ##NaN ##NaN)
请注意,装箱是在静态块中发生的。
class user$fn_line_1__246
Minor version: 0
Major version: 52
Flags: PUBLIC, FINAL, SUPER
public static final java.lang.Object const__1;
Flags: PUBLIC, STATIC, FINAL
public void <init>();
Flags: PUBLIC
Code:
linenumber 1
0: aload_0
1: invokespecial clojure/lang/AFunction.<init>:()V
4: return
public static java.lang.Object invokeStatic();
Flags: PUBLIC, STATIC
Code:
linenumber 1
0: getstatic user$fn_line_1__246.const__1:Ljava/lang/Object;
3: getstatic user$fn_line_1__246.const__1:Ljava/lang/Object;
linenumber 1
6: invokestatic clojure/lang/Util.identical:(Ljava/lang/Object;Ljava/lang/Object;)Z
9: ifeq 18
12: getstatic java/lang/Boolean.TRUE:Ljava/lang/Boolean;
15: goto 21
18: getstatic java/lang/Boolean.FALSE:Ljava/lang/Boolean;
21: areturn
StackMapTable: 00 02 12 42 07 00 17
public java.lang.Object invoke();
Flags: PUBLIC
Code:
linenumber 1
0: invokestatic user$fn_line_1__246.invokeStatic:()Ljava/lang/Object;
3: areturn
static {};
Flags: PUBLIC, STATIC
Code:
linenumber 1
0: ldc2_w NaN
3: invokestatic java/lang/Double.valueOf:(D)Ljava/lang/Double;
6: putstatic user$fn_line_1__246.const__1:Ljava/lang/Object;
9: return
示例 4 (let [x ##NaN] (identical? x x))
注意调用静态方法中的两次Double.valueOf
调用。
class user$fn_line_1__250
Minor version: 0
Major version: 52
Flags: PUBLIC, FINAL, SUPER
public void <init>();
Flags: PUBLIC
Code:
linenumber 1
0: aload_0
1: invokespecial clojure/lang/AFunction.<init>:()V
4: return
public static java.lang.Object invokeStatic();
Flags: PUBLIC, STATIC
Code:
linenumber 1
0: ldc2_w NaN
3: dstore_0 /* x */
4: dload_0 /* x */
5: invokestatic java/lang/Double.valueOf:(D)Ljava/lang/Double;
8: dload_0 /* x */
9: invokestatic java/lang/Double.valueOf:(D)Ljava/lang/Double;
linenumber 1
12: invokestatic clojure/lang/Util.identical:(Ljava/lang/Object;Ljava/lang/Object;)Z
15: ifeq 24
18: getstatic java/lang/Boolean.TRUE:Ljava/lang/Boolean;
21: goto 27
24: getstatic java/lang/Boolean.FALSE:Ljava/lang/Boolean;
27: areturn
StackMapTable: 00 02 FC 00 18 03 42 07 00 1B
public java.lang.Object invoke();
Flags: PUBLIC
Code:
linenumber 1
0: invokestatic user$fn_line_1__250.invokeStatic:()Ljava/lang/Object;
3: areturn
static {};
Flags: PUBLIC, STATIC
Code:
linenumber 1
0: return