是的,相信我,这是我们长时间讨论的问题,并且在某些程度上已经以各种变体实现了。
主要问题是编译器实际上并不知道表达式的类型,它只知道报告的类型,这可能会来自提示类型流程,这可能与之后的实际运行时情况不一致。
具体示例
(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上,如果您恰好在循环中,这一切都将被优化并正确预测。