ove*_*ink 14 types clojure variadic-functions
在回答另一个问题时,我遇到了一些我没想到的Clojure的变量arity函数args:
user=> (defn wtf [& more] (println (type more)) :ok)
#'user/wtf
;; 1)
user=> (wtf 1 2 3 4)
clojure.lang.ArraySeq
:ok
;; 2)
user=> (let [x (wtf 1 2 3 4)] x)
clojure.lang.ArraySeq
:ok
;; 3)
user=> (def x (wtf 1 2 3 4))
clojure.lang.PersistentVector$ChunkedSeq
#'user/x
user=> x
:ok
Run Code Online (Sandbox Code Playgroud)
为什么ArraySeq1)和2)中的类型,但PersistentVector$ChunkedSeq在3)?
Ale*_*lex 12
简短的回答:这是Clojure的一个模糊的实现细节.该语言唯一保证的是,可变函数的rest-param将作为实例传递clojure.lang.ISeq,或者nil如果没有其他参数.你应该相应地编码.
答案很长:它与函数调用是编译还是简单评估有关.如果不完整地讨论评估和编译之间的区别,那么知道Clojure代码被解析为AST就足够了.根据上下文,AST中的表达式可以直接进行评估(类似于解释),也可以作为动态生成的类的一部分编译成Java字节码.后者发生的典型情况是lambda表达式的主体,它将评估为实现IFn接口的动态生成的类的实例.有关评估的更详细说明,请参阅Clojure文档.
绝大多数情况下,编译代码和评估代码之间的差异对于您的程序是不可见的; 他们的行为方式完全相同.这是罕见的极端情况之一,其中编译和评估导致微妙的不同行为.然而,重要的是要指出两种行为都是正确的,因为它们符合语言所作的承诺.
Clojure代码中的函数调用被解析为InvokeExprin 的实例clojure.lang.Compiler.如果正在编译代码,则编译器会发出字节码,该字节码将使用适当的arity 调用对象invoke上的方法IFn(Compiler.java,第3650行).如果代码只是被评估而不是编译,那么函数参数被捆绑在一起PersistentVector并传递给对象applyTo上的方法IFn(Compiler.java,第3553行).
具有可变参数arg列表的Clojure函数被编译到类的子clojure.lang.RestFn类中.此类实现IFn,收集参数和调度到适当的doInvokearity的所有方法.您可以在执行中applyTo看到,在0需要args的情况下(如wtf函数中的情况),输入seq将传递给doInvoke方法并对函数实现可见.invoke同时,4-arg版本捆绑了a中的参数ArraySeq并将其传递给doInvoke方法,所以现在你的代码看到了ArraySeq.
更复杂的是,Clojure的执行eval功能(这是什么REPL呼吁)将在内部包裹一个thunk内被评估以列表的形式(一anoymous,无参数的功能),然后编译并执行的thunk.因此,几乎所有调用都使用对invoke方法的编译调用,而不是由编译器直接解释.对于def没有编译显式评估代码的表单有一个特例,它解释了您在那里看到的不同行为.
执行clojure.core/apply也调用applyTo方法,并且通过这个逻辑,传递给任何列表类型的apply应该是函数体.确实:
user=> (apply wtf [1 2 3 4])
clojure.lang.PersistentVector$ChunkedSeq
:ok
user=> (apply wtf (list 1 2 3 4))
clojure.lang.PersistentList
:ok
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
569 次 |
| 最近记录: |