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

欢迎!请访问关于页面,了解有关此工作的更多信息。

+1
Clojure

查看新的函数式接口支持时,我发现即使在编译器应该知道表达式已经支持函数式接口的情况下(例如,当表达式是适当的reify实例时),也会发出使用Java lambda进行的检查。

为了避免不必要的检查,我将Compiler.java中的maybeEmitFIAdapter改为添加以下编译时测试。更改后,Clojure仍然可以干净构建并通过所有测试。我不是编译器或ASM专家,所以这可能不是做这件事的最好方法,但我认为应该考虑类似的方法。

		// if(exp instanceof IFn) { emitInvokeDynamic(targetMethod, fnInvokerMethod) }
		expr.emit(C.EXPRESSION, objx, gen);
		// FOLLOWING TEST ADDED:
		if (!(expr.hasJavaClass() && targetClass.isAssignableFrom(expr.getJavaClass()))) {
			gen.dup();
			gen.instanceOf(ifnType);

			// if not instanceof IFn, go to end
			Label endLabel = gen.newLabel();
			gen.ifZCmp(Opcodes.IFEQ, endLabel);

			// else adapt fn invoker method as impl for target method
			emitInvokeDynamicAdapter(gen, targetClass, targetMethod, FnInvokers.class, fnInvokerMethod);

			// end - checkcast that we have the target FI type
			gen.mark(endLabel);
		}
		gen.checkCast(samType);

2 答案

+2

已选择
 
最佳答案

是的,请相信我,这确实是我们讨论了好久的事情,并且它已经以某种形式得到了实施。

关键是编译器实际上不知道表达式的类型,它只知道正在报告的类型,而且这可能来自提示类型流,而这些流可能与后来的运行时现实不符。

具体例子

(defn remove [^Collection coll ^Predicate pred]
  (.removeIf coll pred)))

在这里,removeIf的调用需要一个Predicate和表达式类型,pred局部变量的类型将是Predicate。但是,Clojure将这些提示视为提示,而不是静态类型签名,你可能会传递一个实际的Predicate,或者你可能会传递一个IFn。在发出代码中的类型检查允许两者透明地工作。

更微妙的是,你甚至可能只在 pred 参数上提供了类型提示: (.removeIf coll ^Predicate pred) 以解决重载(在此情况下不是,但有一些类似的情况)尽管你知道 pred 不是 Predicate,而 FI 适配是您想要的。解决重载情况的一种可能方法是通过新参数标签元数据来指示首选重载 (^[_ Predicate] Collection/.removeIf coll pred) 但这需要非常巧妙地理解和应用。

我们还研究了调用和 let 绑定情况的不同行为,也许为 let 实现这一功能,但不针对调用。最终,唯一安全的事情是在运行时实际上进行检查。

接下来要问的问题是检查的成本,而且总的来说,在我的性能测试中,我找不到可检测的性能成本 - 你通常是将函数传递给其他一些是真实成本驱动器的东西(例如,将 Predicate 传递给 .filter,将 Function 传递给 .map 或类似的东西)。特别是在较新的 JVM 和 CPU 上,如果你碰巧在一个循环中,所有这些都将被优化并正确预测。

–1 投票
实际上完全是另一回事。
...