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

欢迎!请查看 关于 页面以了解更多有关此信息的工作方式。

+1
编译器

当使用 loop,看起来 Clojure 编译器试图使用原生变量(原生类型)以获得更好的性能。从 "对 Java 原生类型支持" 部分。

let/loop 绑定的本地变量可以是原生类型,其可能是其初始化表达式的原生类型。
重新绑定原生本地变量的 recur 表达式不会进行装箱处理,并执行相同原生类型的类型检查。

下面的循环抛出语法错误。

(loop [x (pos? 1)]
  (when-not x
    (recur (first [false]))))
Syntax error (IllegalArgumentException) compiling fn* at (src/foo.clj:4:1).
 recur arg for primitive local: x is not matching primitive, had: Object, needed: boolean

编译器似乎知道 pos? 返回一个原生类型的 boolean,而 (first [false]) 并不是(返回装箱的布尔类型 Boolean?)。将 recur 参数包裹在 boolean 调用中可以成功编译。

(loop [x (pos? 1)]
  (when-not x
    (recur (boolean (first [false])))))
=> nil

然而,将 recur 参数更改为不同的函数确实很引人注目。

(loop [x (pos? 1)]
  (when-not x
    (recur (pos? x))))
Syntax error (IllegalArgumentException) compiling fn* at (src/foo.clj:4:1).
 recur arg for primitive local: x is not matching primitive, had: Object, needed: boolean

由于编译器知道 pos? 在初始化表达式中返回原生类型,所以似乎它也应该知道在 recur 参数中也会返回原生类型。

与之前一样,将 pos? 的 recur 调用包裹在 boolean 中允许该表单编译。

(loop [x (pos? 1)]
  (when-not x
    (recur (boolean (pos? x)))))
=> nil

如果我将参数更改为一个字面量,我会得到语法错误。

(loop [x (pos? 1)]
  (when-not x
    (recur (boolean 1))))
Syntax error (IllegalArgumentException) compiling fn* at (src/foo.clj:4:1).
 recur arg for primitive local: x is not matching primitive, had: Object, needed: boolean

但这只适用于数字。传递一个字符串、字符、向量,甚至是布尔值都可以编译。例如:

(loop [x (pos? 1)]
  (when-not x
    (recur (boolean true))))
=> nil

传递字面量布尔值不起作用。

(loop [x (pos? 1)]
  (when-not x
    (recur false)))
Syntax error (IllegalArgumentException) compiling fn* at (src/foo.clj:4:1).
 recur arg for primitive local: x is not matching primitive, had: java.lang.Boolean, needed: boolean

pos? 是一个内联函数。在上面的情况下它扩展到 (. clojure.lang.Numbers (isPos 1))。从 isPos 的源代码中,我们发现一个具有返回类型 boolean 的静态方法。

static public boolean isPos(Object x){
	return ops(x).isPos((Number)x);
}

由于 isPos 是一个静态方法,编译器应该始终知道它的类型。从 类型提示

请注意,类型提示对于静态成员(或它们的返回值!)不是必需的,因为编译器始终有静态的类型。

但是,除了第一个示例和带有字面量示例之外,上述所有函数都调用返回原生布尔类型的静态 Java 方法。为什么编译器在上面的示例中没有看到原生类型返回值呢?

2 答案

+1

编辑

这不是答案,而是对问题的某些改进

看起来 truefalse 被封装(至少在这个例子中)为 java.lang.Boolean,因此这行得通

user> (loop [x  (pos? 1)]
         (when-not x 
             (recur (.booleanValue true))))
nil

我认为 booleanCast有些不太明显的问题。由于它得到一个字面 Long 值1,它可能正在沿着对象路径走下去,根据 x != null önidicate 结果。似乎这 somehow 在某种原因引入了原始类型转换中的细微差异。

如果我们双重转换boolean会怎样?

user> (loop [x  (pos? 1)]
    (when-not x 
        (recur  (boolean (boolean 1)))))
nil

奇怪的是,“表现不错”,因为我们对原始的布尔值进行布尔运算,这是一个身份...

我打赌对编译器更熟悉的人会有所见解,以及这是一个错误还是一项功能。对我来说,这似乎是一个错误。

此外,通过提示,可以使编译器抱怨需要进行 boolean 类型转换而不是 boolean 类型转换。奇怪。

user> (loop [x  (pos? 1)]
    (when-not x 
        (recur  ^boolean (boolean 1))))
Syntax error (IllegalArgumentException) compiling fn* at (*cider-repl 
workspacenew\test:localhost:52469(clj)*:810:7).
 recur arg for primitive local: x is not matching primitive, 
had: boolean, needed: boolean
转移到答案中
+1

有三种情况
1. 正常函数调用
2. 作为方法内联调用
3. 作为反射方法调用内联

情况1的返回类型为布尔函数的Object,这是任何函数的默认返回类型,没有反射
(即任何函数的默认返回类型),没有反射;参数传递为对象。
发生情况

情况2内联方法的返回类型,没有反射发生,因为编译器可以静态推断所有参数的类型
因为编译器可以静态推断所有参数的类型
发生情况

情况3的返回类型为Object,因为编译器不知道实际上正在调用哪个方法,它所有都是在
和运行时确定
情况

考虑到这一点,我将尝试按照要点逐点回答原始问题

  1. 编译器不知道 pos? 的返回类型。它知道调用的方法 clojure.lang.Numbers/isPos 的返回类型,该类型可能会或不可能会内联到 pos?
  2. 同样,编译器不知道boolean返回布尔值,它知道clojure.lang.RT/booleanCast返回布尔值,有时将boolean内联为对该方法的调用。
  3. 当将pos?内联为对clojure.lang.Numbers/isPos的调用时,该方法为Object、long和double类型的参数定义,不是boolean类型,因此当你传入静态知道是布尔类型的参数时,你将得到对该方法的一个反射调用。
  4. 再次,当将boolean内联为对clojure.lang.RT/booleanCast的调用时,该方法为Object和boolean类型定义,不是long类型,因此你会得到对该方法的反射调用,这意味着返回类型未知或为Object。
  5. 字面值都是包装化的,所以false不是布尔值,它是一个Boolean。

至于澄清:除非最近有所变化
^boolean不是一个有效的Clojure类型提示(在ClojureScript中可能有效
)。

...