请在 2024 Clojure 状态调查! 分享您的看法。

欢迎!有关如何使用本网站的信息,请参阅 关于 页面。

+2
Java 互操作性

考虑以下使用 Clojure 1.11.1 解释的代码片段。

;; example 1
user=> (identical? Double/NaN Double/NaN)
false

;; example 2
user=> (let [x Double/NaN] (identical? x x))
false

;; example 3
user=> (identical? ##NaN ##NaN)
true

;; example 4
user=> (let [x ##NaN] (identical? x x))
false

为什么示例 3 返回 true,而其他所有实例都返回 false?这是预期的行为吗?

我认为这与 Clojure 有时使用无包装数学的方式有关。

在我的 Clojure 心智模型中,identical? 基本上是 Java 的 ==,而 ##NaN 是 Java 的 Double.NaN。在 Java 中,Double.NaN == Double.NaN 的评估结果为 false。因此,我预计所有实例都将返回 false。

2 答案

+3
 
最佳答案

好吧,我对 identical? 的认知不准确。它调用了 clojure.lang.Util/identical,其实现如下

static public boolean identical(Object k1, Object k2){
    return k1 == k2;
}

当 Clojure 调用一个具有此签名的函数时,它必须将参数中的原始值装箱。Double 通过调用 Double.valueOf(double) 来装箱。NaN 的不同等价语义使示例变得难以理解,但在每种情况下,identical? 都比较装箱值的引用等价性,而不是数值等价性。

通过查看反汇编的 bytecode(见下文),我们可以看到发生了什么。以下是摘要。

  • 在示例 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? 的理解是错误的。它更像是 (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))

在 invokeStatic 方法中有两个 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         
很有趣。我对 `identical?` 的心理模型是准确的(`identical?` 返回相同的对象),但对我对 Clojure 参数传递的心理模型有误。看看你的示例,以下是我认为应该发生的事情。

```clojure
;; 示例1
user=> (identical? Double/NaN Double/NaN)
false
;; false 可能是正确的。如果 Double/NaN 被标记为 final,那么应该是 true,但它显然不是,尽管 Double 类被标记为 final。

;; 示例2
user=> (let [x Double/NaN] (identical? x x))
false
;; 我认为 Clojure 编译器应该只装箱一次,并将相同的 box 传递给 identical?,从而返回 true

;; 示例3
user=> (identical? ##NaN ##NaN)
true
;; 我很高兴这是真的,但很惊讶这被优化为一个特殊情况。

;; 示例4
user=> (let [x ##NaN] (identical? x x))
false
;; 与示例2一样,Clojure 编译器应该足够智能,一次装箱,从而返回 true。
```

基本上,我的直觉是如果 `x` 被绑定到一个值,无论装箱与否,该等价对象都应该传递给被调用函数。纯粹从效率的角度来看,这与 `identical?` 无关,编译器不应多次对相同的原始值进行装箱。因此,我认为这是一个问题,Clojure 编译器没有生成直观或最佳的代码。
0

在示例3中,读取器将这两个常量绑定到同一个 Object 常量下,因此只有一个 Object 类型的实例。

在其他例子中,也存在 double 类型的实例。

所有这些,这正是 Java 的工作方式:https://stackoverflow.com/a/1408643/564509

以下是演示 Java 行为的简单方法

jshell> Object a = Double.NaN
a ==> NaN

jshell> a == a
$2 ==> true

jshell> double a = Double.NaN
a ==> NaN

jshell> a == a
$4 ==> false
你是如何得出这个结论的?
如果你指的是我的消息中的前两条声明,那么我已经获取了你的 Clojure 代码,编译、反编译,并检查了结果。
你使用了 clj-java-decompiler 的反编译(Java 输出)吗?也尝试使用 disassemble(字节码输出),它与你的解释并不完全匹配。
我使用了内置在 IntelliJ IDEA 中的反编译器。但使用的工具并不会造成太大影响。最后,你甚至可以使用一些查看器检查字节码。为了提高确定性,你可以使用 `def` 将你的 OP 中的每个表达式绑定到名称。
...