Clojure

求值

求值可以在多种上下文中发生

  • 交互式地,在 REPL 中

  • 从流中读取的一系列表单,通过 load / load-file / load-reader / load-string

  • 以编程方式,通过 eval

Clojure 程序由表达式组成。编译器将每个不属于特殊形式或宏的表单视为表达式,该表达式会被求值以产生一个值。虽然有时表达式会被求值以获得其副作用并忽略其值,但没有声明或语句。在所有情况下,求值都是相同的 - 编译器会考虑单个对象,对其进行求值,并返回其结果。如果需要编译表达式,则会进行编译。没有单独的编译步骤,也不需要担心您定义的函数是否正在被解释。Clojure 没有解释器

字符串、数字、字符、truefalsenil 和关键字都求值为自身。

符号会被解析

  • 如果它是命名空间限定的,则其值为该符号所命名的全局 Var 的绑定值。如果不存在该符号所命名的全局 Var,或者引用的是不同命名空间中的非公共 Var,则会发生错误。

  • 如果它是包限定的,则其值为该符号所命名的 Java 类。如果不存在该符号所命名的类,则会发生错误。

  • 否则,它没有限定,并且以下第一个条件适用

    1. 如果它命名了一个特殊形式,则它被视为一个特殊形式,并且必须相应地使用。

    2. 如果在本地作用域中(例如,在函数定义或 let 表达式中),则会进行查找以查看它是否命名了本地绑定(例如,函数参数或 let 绑定的名称)。如果是,则其值为本地绑定的值。

    3. 在当前命名空间中进行查找,以查看是否存在从符号到类的映射。如果是,则该符号被视为命名一个 Java 类对象。请注意,类名通常表示类对象,但在某些特殊形式中会得到特殊处理,例如 .new

    4. 在当前命名空间中进行查找,以查看是否存在从符号到 Var 的映射。如果是,则其值为该符号所引用的 Var 的绑定值。

    5. 这是一个错误。

如果符号具有元数据,则编译器可能会使用它,但它不会成为结果值的一部分。

向量、集合和映射会产生向量和(哈希)集合以及映射,其内容是它们包含的对象的求值结果。向量元素从左到右求值,集合和映射以未定义的顺序求值。元数据映射也是如此。如果向量或映射具有元数据,则求值后的元数据映射将成为结果值的元数据。

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