为什么Clojure变量arity参数根据使用获得不同的类型? [英] Why do Clojure variable arity args get different types depending on use?

查看:162
本文介绍了为什么Clojure变量arity参数根据使用获得不同的类型?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在回答另一个问题我遇到了一些我没有想到的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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆