为什么Clojure变量arity参数根据使用获得不同的类型? [英] Why do Clojure variable arity args get different types depending on use?
问题描述
在回答另一个问题我遇到了一些我没有想到的Clojure的变量arity函数args:
In answering another question I came across something I didn't expect with Clojure's variable arity function 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
为什么类型 ArraySeq
in 1)and 2),but PersistentVector $ ChunkedSeq
in 3)?
Why is the type ArraySeq
in 1) and 2), but PersistentVector$ChunkedSeq
in 3)?
推荐答案
简短答案:这是Clojure的一个模糊实现细节。该语言唯一保证的是,可变参数的其余参数将作为 clojure.lang.ISeq
或的实例传递。 nil
如果没有其他参数。你应该相应地编码。
Short answer: It's an obscure implementation detail of Clojure. The only thing guaranteed by the language is that the rest-param of a variadic function will be passed as an instance of clojure.lang.ISeq
, or nil
if there are no additional arguments. You should code accordingly.
长回答:它与函数调用是编译还是简单评估有关。没有完整的论述评估和编译之间的差异,应该足够知道Clojure代码被解析为一个AST。根据上下文,AST中的表达式可以直接求值(类似于解释),或者可以编译为Java字节码作为动态生成类的一部分。后者发生的典型情况是在lambda表达式的主体中,它将评估实现 IFn
接口的动态生成类的实例。有关评估的详细说明,请参阅 Clojure文档。
Long answer: It has to do with whether the function call is compiled or simply evaluated. Without going into a full dissertation on the difference between evaluation and compilation, it should be sufficient to know that Clojure code gets parsed into an AST. Depending on the context, expressions in the AST could get evaluated directly (something akin to interpretation), or could get compiled into Java bytecode as part of a dynamically-generated class. The typical case where the latter happens is in the body of a lambda expression, which will evaluate to an instance of a dynamically generated class that implements the IFn
interface. See the Clojure documentation for a more detailed explanation of evaluation.
绝大多数时间,编译和评估代码之间的区别对于你的程序是不可见的;他们将以完全相同的方式表现。这是其中一个罕见的角落情况下,编译和评估产生微妙的不同的行为。非常重要的是要指出,这两种行为都是正确的,因为它们符合语言所做的承诺。
The vast majority of the time, the difference between compiled and evaluated code will be invisible to your program; they will behave in exactly the same way. This is one of those rare corner cases where compilation and evaluation result in subtly different behavior. It's important to point out, though, that both behaviors are correct in that they conform to the promises made by the language.
Clojure代码中的函数调用被解析成一个实例在
clojure.lang.Compiler
中的 InvokeExpr
。如果代码正在被编译,则编译器发出字节码,它将在 IFn
对象上调用 invoke
适当的权限(编译器。 java,line 3650 )。如果代码只是被评估和未编译,则函数参数被捆绑在 PersistentVector
中,并传递给 applyTo
cn> IFn
对象( Compiler.java,line 3553 )。
Function calls in Clojure code get parsed into an instance of InvokeExpr
in clojure.lang.Compiler
. If the code is being compiled, then the compiler emits bytecode that will call the invoke
method on an IFn
object using an appropriate arity (Compiler.java, line 3650). If the code is just being evaluated and not compiled, then the function arguments are bundled up in a PersistentVector
and passed to the applyTo
method on the IFn
object (Compiler.java, line 3553).
有可变参数的Clojure函数列表将编译为的子类 clojure.lang.RestFn
类。这个类实现 IFn
的所有方法,收集参数,并分派到适当的 doInvoke
arity。你可以在 applyTo
的实现中看到,在0必需的args的情况下(如你的 wtf
function),输入seq被传递到 doInvoke
方法,并且对函数实现是可见的。同时, invoke
的4-arg版本捆绑了 ArraySeq
中的参数,并将其传递给< c $ c> doInvoke 方法,因此现在您的代码看到一个 ArraySeq
。
Clojure functions that have a variadic arg list are compiled into subclasses of the clojure.lang.RestFn
class. This class implements all the methods of IFn
, gathers arguments, and dispatches to the appropriate doInvoke
arity. You can see in the implementation of applyTo
that, in the case of 0 required args (as is the case in your wtf
function), the input seq is passed through to the doInvoke
method and visible to the function implementation. The 4-arg version of invoke
, meanwhile, bundles up the arguments in an ArraySeq
and passes this to the doInvoke
method, so now your code sees an ArraySeq
.
复杂的事情,Clojure的 eval
函数(这是REPL正在调用的)的实现将内部包裹一个列表形式在thunk内评估(anoymous,no-arg函数),然后编译并执行thunk。因此,几乎所有调用都使用对 invoke
方法的编译调用,而不是直接由编译器解释。有一个特殊的情况, def
表单显式评估代码而不编译,这说明你看到的不同的行为。
To complicate matters, the implementation of Clojure's eval
function (which is what the REPL is calling) will internally wrap a list form being evaluated inside a thunk (an anoymous, no-arg function), then compile and execute the thunk. So almost all invocations are using compiled calls to the invoke
method, rather than being interpreted directly by the compiler. There's a special case for def
forms that explicitly evaluates the code without compiling, which accounts for the different behavior you're seeing there.
clojure.core / apply
的实现也调用了 applyTo
方法,列表类型传递给 apply
应该看到的是函数体。确实:
The implementation of clojure.core/apply
also calls the applyTo
method, and by this logic whatever list type passed to apply
should be seen the the function body. Indeed:
user=> (apply wtf [1 2 3 4])
clojure.lang.PersistentVector$ChunkedSeq
:ok
user=> (apply wtf (list 1 2 3 4))
clojure.lang.PersistentList
:ok
这篇关于为什么Clojure变量arity参数根据使用获得不同的类型?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!