Clojure的编译器不是一个完整的程序编译器。编译的单位是顶级形式。因此,即使你说“编译这个文件”,实际上发生的做法是该文件中的每个顶级形式都会单独编译,而编译器一次只能“看到”一个顶级形式。
函数是闭包,所以它们不仅仅是代码(像静态方法一样),它们是代码+封闭的值(像实例方法一样,封闭的值是实例的字段)。
例如
(fn [x] (fn [y] [x y]))
此代码包含一个内层函数和一个外层函数,编译成两个类。每次调用外层函数时,都会创建内层函数的一个新实例,该实例在构造时接收x的值,并将x存储在某些实例字段中。
使用静态方法实现fns的另一个问题是,fns是一等公民,而静态方法不是。你不能将静态方法作为参数传递等。有一些静态方法的一等公民表示方式,例如反射的Method和indiy的MethodHandles,但反射被认为速度较慢,而MethodHandles在clojure首次创建时并未可用。在任何情况下,这两种表示方法都不能用来实现clojure的函数接口IFn,因此需要大量的工作才能使它们正常工作(可能需要重新设计clojure调用函数的方式以使用动态调用)。
在一般情况下,函数需要能够封装值(实例字段)并需要有某种一等公民表示(实例),所以静态方法不行。
但clojure编译器确实在一些优化中做了工作,它可以为函数生成一个静态方法,并在某些情况下将函数调用转换为静态方法调用。但它仍然生成了实例方法版本(仅调用静态方法),因为编译器并不总能确定正在调用的静态方法是什么,因此函数必须都支持相同的通用调用约定(即实现IFn的实例)。
我已经在进行一项关于https://clojure.atlassian.net/browse/CLJ-701的方法研究工作,我还没有发布任何补丁(它仍然不起作用),这会导致编译器更积极地用静态方法替换某些函数定义和调用,并完全消除了为这些函数生成单独类的需求,但它应用范围极其有限,仅限于立即在其定义位置调用的函数,且其定义不脱离。这不会覆盖顶层定义,因为def会让函数值全局化,这就像是定义逃离的范畴。