REPL = 读取-求值-输出循环。逐步了解读取和求值过程。
读取:Clojure看到字符串"(`a)"
,对其进行解析,最终得到一个数据结构。在读取时,阅读器宏会被展开,除此之外不会发生太多其他事情。在本例中,阅读器展开反引号并最终得到以下结果:
user> (read-string "(`a)")
((quote user/a))
EVAL: Clojure会尝试评估这个对象。评估规则取决于所考虑的对象类型。
- 一些对象作为它们自己进行评估(例如数字、字符串、关键字等)。
- 符号通过在某个命名空间中解析来评估它,以获取某个值(通常情况下)。
- 列表通过宏展开列表,直到没有宏剩余,然后递归地评估列表中的第一个项以获取某个结果值,然后使用列表中第一个项的值来决定该做什么。如果第一个值是一种特殊形式,则会发生特殊情况。否则,第一个值被视为函数,并用列表其余部分的值(通过递归地评估列表的所有项)作为参数来调用函数。
- 等等。
有关列表的评估规则,请参见Clojure源代码中的clojure.lang.Compiler/analyzeSeq
,有关符号的评估规则,请参见clojure.lang.Compiler/analyzeSymbol
。那里还有许多其他评估规则。
例子
假设你这样做:
user> (user/a)
REPL 内部最终会执行以下操作:
user> (eval '(user/a))
Clojure会发现你正在评估一个列表,因此它会评估列表中的所有项。第一个(也是唯一的)项目:
user> (eval 'user/a)
#<user$a__1811 user$a__1811@82c23d>
a
不是一个特殊形式,这个列表不需要宏展开,因此符号
a
在命名空间
user
中进行查找,
结果值是一个
fn
。因此调用这个
fn
。
你的代码
但实际上你有这个:
user> (eval '((quote user/a)))
Clojure 评估列表中的第一个项,该项本身是一个列表。
user> (eval '(quote user/a))
user/a
这段代码评估了子列表中的第一个元素 quote
,它是一种特殊形式,因此应用了特殊规则,并返回其参数(Symbol a
)未经评估的结果。
在这种情况下,符号 a
是值,因为上面的 fn
是该值。 所以Clojure将符号本身视为函数并调用它。在Clojure中,任何实现Ifn
接口的东西都可以像fn
一样被调用。恰好clojure.lang.Symbol
实现了Ifn
接口。作为函数调用的符号期望一个参数集合,并在该集合中查找自己。它的使用方法如下:
user> ('a {'a :foo})
:foo
这里的操作是试图实现什么。但是您没有传递任何参数,因此会出现错误“传递给Symbol的参数数量不正确”(它需要一个集合)。
要使您的代码起作用,您需要两个级别的eval
。 这将起作用,希望您能够看到原因:
user> (eval '((eval (quote user/a))))
Hello, world
user> ((eval (first l)))
Hello, world
请注意,在真正的代码中,直接使用eval
通常是一个非常糟糕的想法。宏(macro)是更好的选择。我只是在此处使用它进行演示。
查看Clojure源代码中的Compiler.java
,了解这一切是如何进行的。这并不太难跟随。