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的==,而##NaNJava的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.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?的心智模型是错误的。它更像是(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?`对于同一个对象返回true),但对我对Clojure参数传递的心智模型存在缺陷。根据你的示例,在我看来,以下应该是发生的:

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

;; 示例 2
user=> (let [x Double/NaN] (identical? x x))
false
;; 我认为 Clojure 编译器应该装箱一次,然后将相同的箱传递给 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
by
你是如何得出这个结论的?
by
如果你指的是我的信息中的前两句话,那么我已经获取了你的Clojure代码,编译、反编译并检查了结果。
by
你是否使用了clj-java-decompiler的反编译(Java 输出)?也尝试一下disassemble(字节码输出),它并不真的符合你的解释。
by
我使用了集成在IntelliJ IDEA中的反编译器。但是,你所使用的工具不应该是如此重要。最后,你甚至可以用一些查看器检查字节码。为了提高确定性,你可以使用`def`将你的OP中的每个表达式绑定到一个名字。
...