【发布时间】:2011-01-20 04:36:23
【问题描述】:
Clojure 中的符号绑定到底层对象并具有可选的单独值的基本原理是什么?也许我缺少一些基本的东西,但如果有人能指出为什么会很棒。
【问题讨论】:
标签: clojure
Clojure 中的符号绑定到底层对象并具有可选的单独值的基本原理是什么?也许我缺少一些基本的东西,但如果有人能指出为什么会很棒。
【问题讨论】:
标签: clojure
任何 Lisp 中的符号都被用作标识符。如果你要引用一个变量的值,比如说,你需要有一种命名它的方法;这就是符号的用途。请记住,所有 Lisp 代码在读取时都会被转换为 Lisp 数据结构;标识符也必须由某种数据结构表示,它恰好是符号。在遇到符号时,eval 会调度到某种“名称查找”操作。
从 Lisp 的一般性转移到 Clojure 的细节,Clojure eval / 编译器的行为是,在遇到符号时,它会将其作为let 引入的局部变量或函数参数的名称或 命名空间中条目的名称。实际上只能在第一个容量中使用非命名空间限定符号(表示foo 形式的符号而不是some-namespace/foo)。
对于非命名空间限定符号foo,如果找到名称为foo 的let 绑定/函数参数,则该符号计算为其值。如果不是,则符号转换为*ns*/foo 形式(*ns* 表示当前命名空间)并尝试在*ns* 中查找相应条目;如果有这样的条目,则返回其值,如果没有,则抛出异常。
请注意,像identity 这样的符号,当在命名空间quux 中使用时,将通过一个中间步骤解析为clojure.core/identity,在该步骤中会发现quux/identity 下的条目;这通常参考到clojure.core/identity。这是一个在直观编码时不会想到的实现细节,但在试图解释这一点时我发现不可能不提。
一个已经被命名空间限定的符号(类似于 refers 到 clojure.zip 没有 use 的命名空间中的 zip/root)将在适当的命名空间中查找。
宏增加了一些复杂性(只能出现在操作符位置),但这与符号本身的行为并不真正相关。
请注意,在 Clojure 中,符号本身并不是存储位置——Var 是。因此,当我在上面说在命名空间中查找符号时,我的意思是eval 查找由解析为其命名空间限定形式的符号命名的 Var,然后取其值。特殊形式 var(通常缩写为 #')修改了这种行为,以便返回 Var 对象本身。不过,符号到变量解析的机制没有改变。
请注意,所有这些都意味着符号仅“绑定”到对象,因为eval 在评估符号时会继续寻找更多对象。符号本身没有用于绑定对象的“槽”或“域”;符号被“绑定”到某个对象的任何印象都是由于eval 的工作原理。这是 Clojure 的一个特性,因为在一些 Lisps 中,符号本身就充当了存储位置。
最后,可以使用通常的引用机制来阻止对符号的求值:在'foo 中,符号foo 将不会被求值(因此不会执行任何类型的名称查找);它将原封不动地返回。
回应 OP 的评论:试试这个好玩:
(defmacro symbol?? [x]
(if (symbol? x)
true
false))
(def s 1)
(symbol? s)
; => false
(symbol?? s)
; => true
(symbol? 's)
; => true
(symbol?? 's)
; => false
最后一个解释:'s是(quote s)的简写;这是一个列表结构,而不是一个符号。宏对其直接传入的参数进行操作,无需评估;所以在(symbol?? 's) 中,它实际上看到了(quote s) 列表结构,它本身当然不是一个符号——尽管当传递给eval 时,它的计算结果为一个。
【讨论】:
(symbol? s),s 在symbol? 函数看到它之前被评估(在这种情况下是1),所以你基本上是在问1 是否是一个符号(它不是) .如果您引用该符号,它会原封不动地传递给symbol?。 (eval 对数据结构进行操作,因此在这两种情况下,内存中的符号表示已经由读者构建;引用使得 eval 不对其进行评估)。 Clojure 的函数调用语义是按值调用;你必须使用宏来改变它。事实上,我可能会在一秒钟内编辑一个。 :-)
Common Lisp 和 Clojure 中“符号”一词的不同用法可能会造成一些混淆。
在 Common Lisp 中,“符号”是内存中的一个位置,一个可以存储数据的地方。符号的“值”是存储在内存中该位置的数据。
在 Clojure 中,“符号”只是一个名称。它没有任何价值。
当 Clojure 编译器遇到符号时,它会尝试将其解析为
正如之前的海报所指出的,Var 代表一个存储位置。
Clojure 将 Vars 与 Symbols 分开是有充分理由的。首先,它避免了 Common Lisp 的自动内嵌符号的烦恼,它会用不需要的符号“污染”一个包。
其次,Clojure 变量在并发方面具有特殊的语义。 Var 有一个对所有线程可见的“根绑定”。 (当您键入“def”时,您正在设置 Var 的根绑定。)在线程内(使用“set!”或“binding”)对 Var 所做的更改仅对该线程及其子线程可见。
【讨论】: