请分享您的想法,参与2024年Clojure调查!

欢迎!请查看关于页面以获取更多关于此如何工作的小信息。

+1
Clojure

在检查新的功能接口(Functional Interface)支持时,我注意到即使编译器应该知道该表达式已经支持功能接口(例如,当表达式是一个适当的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适配是你想要的。解决重载情况的一个可能方法是在新的param-tags元数据中指示首选重载 (^[_ Predicate] Collection/.removeIf coll pred) 但这很复杂,难以理解和应用。

我们还检查了调用和let绑定案例的不同行为,也许可以为let做这件事,但不是为调用。最后,唯一安全的事情是实际上在运行时执行检查。

下一个问题是检查的成本,总的来说,在我的性能测试中,我找不到可检测的性能成本 - 您通常是向某些其他东西传递函数,这才能真正驱动成本(将Predicate传递给.filter或将Function传递给.map等)。特别是在较新的JVM和CPU上,如果您恰好在循环中,这一切都将被优化并正确预测。

–1 投票
实际上是完全不同的事情。
...