user=> (def x 1)
user=> (def y 2)
user=> ^{:x x} [x y 3]
^{:x 1} [1 2 3]
求值可以在多种上下文中发生
交互式地,在 REPL 中
从流中读取的一系列表单,通过 load
/ load-file
/ load-reader
/ load-string
以编程方式,通过 eval
Clojure 程序由表达式组成。编译器将每个不属于特殊形式或宏的表单视为表达式,该表达式会被求值以产生一个值。虽然有时表达式会被求值以获得其副作用并忽略其值,但没有声明或语句。在所有情况下,求值都是相同的 - 编译器会考虑单个对象,对其进行求值,并返回其结果。如果需要编译表达式,则会进行编译。没有单独的编译步骤,也不需要担心您定义的函数是否正在被解释。Clojure 没有解释器。
字符串、数字、字符、true
、false
、nil
和关键字都求值为自身。
符号会被解析
如果它是命名空间限定的,则其值为该符号所命名的全局 Var 的绑定值。如果不存在该符号所命名的全局 Var,或者引用的是不同命名空间中的非公共 Var,则会发生错误。
如果它是包限定的,则其值为该符号所命名的 Java 类。如果不存在该符号所命名的类,则会发生错误。
否则,它没有限定,并且以下第一个条件适用
如果它命名了一个特殊形式,则它被视为一个特殊形式,并且必须相应地使用。
如果在本地作用域中(例如,在函数定义或 let 表达式中),则会进行查找以查看它是否命名了本地绑定(例如,函数参数或 let 绑定的名称)。如果是,则其值为本地绑定的值。
在当前命名空间中进行查找,以查看是否存在从符号到类的映射。如果是,则该符号被视为命名一个 Java 类对象。请注意,类名通常表示类对象,但在某些特殊形式中会得到特殊处理,例如 .
和 new
。
在当前命名空间中进行查找,以查看是否存在从符号到 Var 的映射。如果是,则其值为该符号所引用的 Var 的绑定值。
这是一个错误。
如果符号具有元数据,则编译器可能会使用它,但它不会成为结果值的一部分。
向量、集合和映射会产生向量和(哈希)集合以及映射,其内容是它们包含的对象的求值结果。向量元素从左到右求值,集合和映射以未定义的顺序求值。元数据映射也是如此。如果向量或映射具有元数据,则求值后的元数据映射将成为结果值的元数据。
user=> (def x 1)
user=> (def y 2)
user=> ^{:x x} [x y 3]
^{:x 1} [1 2 3]
空列表 ()
求值为一个空列表。
非空列表被视为对特殊形式、宏或函数的调用。调用的形式为 (操作符 操作数*)。
特殊形式是 Clojure 中内置的执行核心操作的原语。如果调用的操作符是一个解析为特殊形式名称的符号,则该调用就是对该特殊形式的调用。在特殊形式下分别讨论每个形式。
宏 是操作表单的函数,允许进行语法抽象。如果调用的操作符是一个命名全局 Var(该 Var 是一个宏函数)的符号,则会调用该宏函数,并向其传递未求值的操作数表单。然后,在宏返回值所在的位置对其进行求值。
如果操作符不是特殊形式或宏,则该调用被视为函数调用。操作符和操作数(如果有)都会从左到右求值。操作符求值的结果会被转换为 IFn(表示 Clojure 函数的接口),并对其调用 invoke(),并将求值后的参数传递给它。invoke() 的返回值是调用表达式的值。如果函数调用表单具有元数据,则编译器可能会使用它,但它不会成为结果值的一部分。请注意,特殊形式和宏可能会对其参数进行非正常的求值,如特殊形式下其条目中所述。
除上述讨论的对象之外的任何对象都将求值为自身。
(load 类路径资源 …)
(load-file 文件名)
(load-reader 读取器)
(load-string 字符串)
以上描述了单个表单的求值。各种 load 表单将依次读取和求值源代码中包含的表单集。此类表单集通常具有副作用,通常对全局环境(定义函数等)产生影响。
加载函数发生在一个临时上下文中,其中 *ns*
具有一个新的绑定。这意味着,如果任何表单对该 Var 产生影响(例如 in-namespace),则该影响将在加载完成后消失。load 等函数会返回最后一个表达式的值。
(eval 表单)
求值表单数据结构(而不是文本!)并返回结果。
(eval (list + 1 2 3))
-> 6