Clojure

Var 和全局环境

Clojure 是一种实用的语言,它认识到偶尔需要维护对变化值的持久引用,并提供 4 种不同的机制以受控方式执行此操作 - Var、RefAgentAtom。Var 提供了一种机制来引用可变的存储位置,该存储位置可以在每个线程的基础上动态重新绑定(到新的存储位置)。每个 Var 都可以(但不必)具有根绑定,根绑定是所有没有每个线程绑定的线程共享的绑定。因此,Var 的值是其每个线程绑定的值,或者,如果在请求值的线程中未绑定,则为根绑定的值(如果存在)。

特殊形式 def 创建(并 驻留)一个 Var。如果 Var 之前不存在并且没有提供初始值,则该 var 未绑定

user=> (def x)
#'user/x
user=> x
#object[clojure.lang.Var$Unbound 0x14008db3 "Unbound: #'user/x"]

提供初始值会绑定根(即使它已经绑定)。

user=> (def x 1)
#'user/x

user=> x
1

默认情况下,Var 是静态的,但 Var 可以标记为动态的,以便通过宏 binding 允许每个线程绑定。在每个线程中,它们都遵循栈规则

user=> (def ^:dynamic x 1)
user=> (def ^:dynamic y 1)
user=> (+ x y)
2

user=> (binding [x 2 y 3]
         (+ x y))
5

user=> (+ x y)
2

使用 binding 创建的绑定无法被任何其他线程看到。同样,使用 binding 创建的绑定可以被赋值,这为嵌套上下文提供了一种与代码通信的方法,然后才能将其放置在调用栈上。此功能仅通过将元数据标签:^:dynamic 设置为 true 才能启用,如上面的代码块所示。在某些情况下,可能希望在上下文中重新定义静态 Var,而 Clojure(从 1.3 版本开始)为这些目的提供了函数 with-redefswith-redefs-fn

使用 defn 定义的函数存储在 Var 中,允许在正在运行的程序中重新定义函数。这也启用了面向方面或面向上下文的编程的许多可能性。例如,您可以在某些调用上下文或线程中仅使用日志记录行为包装函数。

绑定传递

一些 Clojure 并发函数(future、agent)提供“绑定传递”,这允许将当前的动态绑定集传递到另一个线程,以便在具有相同环境的情况下异步继续工作。此功能由 futuresendsend-offpmap 提供。

(def ^:dynamic *num* 1)
(binding [*num* 2] (future (println *num*)))
;; prints "2", not "1"

(set! var-symbol expr)

赋值特殊形式。

当第一个操作数是符号时,它必须解析为全局 var。var 的当前线程绑定的值设置为 expr 的值。目前,尝试使用 set! 设置 var 的根绑定是一个错误,即 var 赋值是线程局部的。在所有情况下,都会返回 expr 的值。

注意 - 您不能为函数参数或局部绑定赋值。在 Clojure 中,只有 Java 字段、Var、Ref 和 Agent 是可变的

Java 交互 中记录了使用 set! 的 Java 字段。

驻留

命名空间系统维护符号到 Var 对象的全局映射(参见 命名空间。如果 def 表达式在当前命名空间中没有为正在 def-ed 的符号找到驻留的条目,则它会创建一个,否则它会使用现有的 Var。此查找或创建过程称为驻留。这意味着,除非它们已被取消映射,否则 Var 对象是稳定的引用,并且不需要每次都查找。这也意味着命名空间构成一个全局环境,如 求值 中所述,编译器试图将所有自由符号解析为 Var。

特殊形式 var#' 读取器宏(参见 读取器可用于获取驻留的 Var 对象而不是其当前值。

非驻留 Var

可以通过使用 with-local-vars 创建未驻留的 var。在自由符号解析期间将找不到这些 var,并且必须手动访问它们的值。但它们可以作为有用的线程局部可变单元。

Var 元数据

创建 var 的形式 defdefndefmacro 等使用一组标准的 var 元数据 来描述 var。其中一些表单使用显式语法来接受存储在元数据中的值,但通常您也可以将该元数据作为 var 符号上的映射提供。

常见的 var 元数据键(在 var 定义时都可选)

  • :doc - 文档化 var 的字符串,通常由文档字符串参数设置

  • :added - 文档化添加此 var 的版本的字符串

  • :private - 布尔标志,通常由 defn- 设置,供作者声明该 var 是实现细节的意图。私有 var 可全局访问,但在过滤到非私有 var 的 ns-…​ 函数中不会被引用或列出。

  • :arglists - 参数列表的集合,如果未提供,将自动生成,最常用于记录宏语法

  • :macro - 由 defmacro 自动添加的布尔标志(通常不直接使用)

  • :tag - var 中值的类型标识符(通常是类)或 var 中保存的函数的返回类型。请注意,var 元数据会被求值,因此 var 上的类型提示(如 ^long)将求值为 long 函数,而不是 long 原语类型提示。通常,建议改为在 defn var 的参数列表上使用类型提示。

  • :test - clojure.test 框架使用此键将单元测试附加到 var(通常不直接使用)

  • :dynamic - 表示 var 可以在线程上下文中动态重新绑定(见上文)。使用直接链接进行编译时,动态 var 不会被直接链接。

  • :redef - 表示在使用直接链接进行编译时不应直接链接 var(从而允许重新定义它)

  • :static - 不再使用(最初 var 默认是动态的,现在默认是静态的)

  • :const - 表示 var 是编译时常量,编译器可以将该值内联到使用它的代码中。注意:这很少需要,并且仅适用于编译时的常量(读取,但不求值),例如数字、字符串等(不是类、函数、ref 类型等)。重新定义或动态绑定 const var 不会影响已经编译并加载到运行时的使用该 var 的代码。

有关直接链接和编译期间元数据省略的更多信息,另请参见 编译器选项