如果我这样做:
(eval (let [f (fn [& _] 10)]
`(~f nil)))
Run Code Online (Sandbox Code Playgroud)
它按预期返回 10。
虽然如果我这样做:
(eval (let [f (constantly 10)]
`(~f nil)))
Run Code Online (Sandbox Code Playgroud)
它抛出一个异常:
IllegalArgumentException No matching ctor found for
class clojure.core$constantly$fn__... clojure.lang.Reflector.invokeConstructor
Run Code Online (Sandbox Code Playgroud)
既然两者是等价的,为什么代码总是不起作用?
小智 5
这个问题有两个答案,要真正明白。
首先,为了澄清为什么您的eval表单在第二种情况下没有给出预期结果,请注意 已f被分配为等于函数 (fn [& _] 10)。这意味着当评估该形式时,函数对象将再次评估——可能不是您想要的。
tl;dr:f在绑定时进行评估,并在您创建的表单为eval'd 时再次评估(结果定义不明确)。
一个(匿名函数)有效而另一个失败的原因意味着我们必须查看评估过程的一些内部机制。
当 Clojure 计算对象表达式(如函数对象形成的表达式)时,它使用以下方法,在clojure.lang.Compiler$ObjExpr
public Object eval() {
if(isDeftype())
return null;
try
{
return getCompiledClass().newInstance();
}
catch(Exception e)
{
throw Util.sneakyThrow(e);
}
}
Run Code Online (Sandbox Code Playgroud)
在 REPL 上试试这个:
从匿名函数开始:
user=> (fn [& _] 10)
#<user$eval141$fn__142 user$eval141$fn__142@2b2a5dd1>
user=> (.getClass *1)
user$eval141$fn__142
user=> (.newInstance *1)
#<user$eval141$fn__142 user$eval141$fn__142@ee7a10e> ; different instance
user=> (*1)
10
Run Code Online (Sandbox Code Playgroud)
请注意,newInstance调用Class该类的空构造函数——不带参数的构造函数。
现在尝试一个关闭某些值的函数
user=> (let [x 10] #(+ x 1))
#<user$eval151$fn__152 user$eval151$fn__152@3a565388>
user=> (.getClass *1)
user$eval151$fn__152
user=> (.newInstance *1)
InstantiationException user$eval151$fn__152 [...]
Run Code Online (Sandbox Code Playgroud)
由于闭包的上值是在构造时设置的,因此这种函数类没有空构造函数,并且在没有上下文的情况下创建新的构造函数会失败。
最后看一下源码constantly
user=> (source constantly)
(defn constantly
"Returns a function that takes any number of arguments and returns x."
{:added "1.0"
:static true}
[x] (fn [& args] x))
Run Code Online (Sandbox Code Playgroud)
返回的函数constantly关闭x,因此编译器将无法执行eval此类函数。
tl;dr(再次):没有上值的函数可以通过这种方式进行计算并生成同一函数的新实例。不能像这样评估具有上值的函数。