在回答another question时,我发现了一些我没想到的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
为什么类型ArraySeq
在1)和2)中,但是{3}中的PersistentVector$ChunkedSeq
?
答案 0 :(得分:12)
简短回答:这是Clojure的一个模糊的实现细节。该语言唯一保证的是,如果没有其他参数,可变函数的rest-param将作为clojure.lang.ISeq
的实例传递,或nil
。你应该相应地编码。
长答案:它与函数调用是编译还是简单评估有关。如果不完整地讨论评估和编译之间的区别,那么知道Clojure代码被解析为AST就足够了。根据上下文,AST中的表达式可以直接进行评估(类似于解释),也可以作为动态生成的类的一部分编译成Java字节码。发生后者的典型情况是lambda表达式的主体,它将评估为实现IFn
接口的动态生成的类的实例。有关评估的更详细说明,请参阅Clojure documentation。
绝大多数情况下,编译代码和评估代码之间的差异对于您的程序是不可见的;他们的行为方式完全相同。这是罕见的极端情况之一,其中编译和评估导致微妙的不同行为。然而,重要的是要指出两种行为都是正确的,因为它们符合语言所作的承诺。
Clojure代码中的函数调用被解析为InvokeExpr
中clojure.lang.Compiler
的实例。如果正在编译代码,则编译器将使用适当的arity(Compiler.java, line 3650)发出将在invoke
对象上调用IFn
方法的字节码。如果代码只是被评估而不是编译,那么函数参数被捆绑在PersistentVector
中并传递给applyTo
对象(Compiler.java, line 3553)上的IFn
方法
具有可变参数arg列表的Clojure函数被编译为clojure.lang.RestFn
类的子类。此类实现IFn
的所有方法,收集参数,并调度到适当的doInvoke
arity。您可以在applyTo
的实现中看到,在0需要args的情况下(如wtf
函数中的情况),输入seq将传递给doInvoke
方法并且对函数实现可见。同时,invoke
的4-arg版本将ArraySeq
中的参数捆绑在一起并将其传递给doInvoke
方法,因此现在您的代码会看到ArraySeq
。< / p>
为了使问题复杂化,Clojure的eval
函数(这是REPL正在调用的函数)的实现将在内部包装一个在thunk(一个anoymous,no-arg函数)中被评估的列表表单,然后编译和执行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
答案 1 :(得分:1)
Clojure在很大程度上没有在类的方面实现,而是在接口和协议方面(对具有一些额外功能的Java接口的Clojure抽象)。
user> (require '[clojure.reflect :as reflect])
nil
user> (:bases (reflect/reflect clojure.lang.ArraySeq))
#{clojure.lang.IndexedSeq clojure.lang.IReduce clojure.lang.ASeq}
user> (:bases (reflect/reflect clojure.lang.PersistentVector$ChunkedSeq))
#{clojure.lang.Counted clojure.lang.IChunkedSeq clojure.lang.ASeq}
好的Clojure代码在ArraySeq
与PersistentVector$ChunkedSeq
方面不起作用,而是调用IndexedSeq
,IReduce
公开的方法或协议函数,ASeq
等,如果他们的论点实现了它们。或者更有可能的是,使用根据这些接口或协议实现的基本clojure.core
函数。
例如,请注意reduce
的定义:
user> (source reduce)
(defn reduce
"f should be a function of 2 arguments. If val is not supplied,
returns the result of applying f to the first 2 items in coll, then
applying f to that result and the 3rd item, etc. If coll contains no
items, f must accept no arguments as well, and reduce returns the
result of calling f with no arguments. If coll has only 1 item, it
is returned and f is not called. If val is supplied, returns the
result of applying f to val and the first item in coll, then
applying f to that result and the 2nd item, etc. If coll contains no
items, returns val and f is not called."
{:added "1.0"}
([f coll]
(clojure.core.protocols/coll-reduce coll f))
([f val coll]
(clojure.core.protocols/coll-reduce coll f val)))
nil
如果你查找coll-reduce
,你会发现基于所实现的接口或协议的各种实现:protocols.clj
(extend-protocol CollReduce
nil
(coll-reduce
([coll f] (f))
([coll f val] val))
Object
(coll-reduce
([coll f] (seq-reduce coll f))
([coll f val] (seq-reduce coll f val)))
clojure.lang.IReduce
(coll-reduce
([coll f] (.reduce coll f))
([coll f val] (.reduce coll f val)))
;;aseqs are iterable, masking internal-reducers
clojure.lang.ASeq
(coll-reduce
([coll f] (seq-reduce coll f))
([coll f val] (seq-reduce coll f val)))
...) ; etcetera