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 投票

被 glchapman 选择
 
最佳答案

是的,信我,这确实是我们讨论已久的事情,这个功能已经以各种各样的形式实现了。

关键是编译器实际上并不了解表达式的类型,它只知道报告的类型,这可以来自提示类型流,而这可能实际上并不匹配后来的运行时现实。

一个具体的例子

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

在这里,调用removeIf需要一个谓词(Predicate)和pred局部变量的表达式类型,它将被视为谓词。但是Clojure将提示视为提示,而不是静态类型签名,因此您可以将实际的谓词传递给它,或者传递IFn。在生成的代码中进行的类型检查允许它们透明地工作。

更微妙的是,您甚至可能只为pred参数提供了类型提示:(.removeIf coll ^Predicate pred) 来解决重载(在这个案例中不是,但有一些是这样的),尽管您知道pred不是一个Predicate,而且FI适应就是您想要的。解决重载情况的一个可能方法是通过使用新的param-tags元数据来指示首选重载 (^[_ Predicate] Collection/.removeIf coll pred) ,但这需要真正聪明才能理解和应用。

我们还检查了调用和声明绑定情况的不同行为,以考虑将此行为应用于let,但不应用于调用。最后,唯一安全的事情是在运行时进行检查。

下一个问题是检查的成本,通常在我的性能测试中,我找不到可检测的性能成本 - 您通常将功能传递给其他某个东西,这是真正的成本驱动因素(将谓词传递给.filter,或将函数传递给.map等)。特别是在较新的JVM和CPU上,如果您在循环中,所有这些都会得到优化和正确预测。

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