为什么Clojure区分符号和变量?
我已经看到了,但它不能解释我在想什么 当我第一次从Common Lisp来到Clojure时,我很困惑为什么它会将符号和关键字作为单独的类型对待,但后来我发现了这一点,现在我认为这是一个很棒的想法。现在我想弄明白为什么符号和变量是分开的对象 据我所知,常见的Lisp实现通常使用以下结构表示“符号”:1)名称为字符串,2)在函数调用位置求值时指向符号值的指针,3)在调用位置之外求值时指向其值的指针,以及4)属性列表,等等 忽略Lisp-1/Lisp-2的区别,事实仍然是在CL中,“符号”对象直接指向其值。换句话说,CL将Clojure所称的“符号”和“变量”组合在一个对象中为什么Clojure区分符号和变量?,clojure,lisp,symbols,Clojure,Lisp,Symbols,我已经看到了,但它不能解释我在想什么 当我第一次从Common Lisp来到Clojure时,我很困惑为什么它会将符号和关键字作为单独的类型对待,但后来我发现了这一点,现在我认为这是一个很棒的想法。现在我想弄明白为什么符号和变量是分开的对象 据我所知,常见的Lisp实现通常使用以下结构表示“符号”:1)名称为字符串,2)在函数调用位置求值时指向符号值的指针,3)在调用位置之外求值时指向其值的指针,以及4)属性列表,等等 忽略Lisp-1/Lisp-2的区别,事实仍然是在CL中,“符号”对象直接指
在Clojure中,要计算符号,首先必须查找相应的var,然后必须取消对var的引用。为什么Clojure是这样工作的?这样的设计有什么好处呢?我知道变量有某些特殊属性(它们可以是私有的、常量的或动态的…),但这些属性不能简单地应用于符号本身吗?对于公共Lisp或其他Lisp,请查看:
与其他Lisp的差异
符号foo
在两种上下文中都是完全相同的符号(即,(='foo(a/foo)(b/foo))
是真的),但在这两种上下文中,它需要携带不同的值(在本例中,是指向两个函数之一的指针)。我从你的帖子中推测了以下问题(告诉我是否偏离了基准):
为什么将符号映射到其基础值时会有两个级别的间接寻址?
当我第一次去回答这个问题时,过了一段时间,我想到了两个可能的原因:“即时重新定义”,以及相关的概念。然而,以下内容使我确信,这两种情况都不是双重间接的原因:
=> (identical? (def a 0) (def a 10))
=> true
=> (declare ^:dynamic bar)
=> (binding [bar "bar1"]
(identical? (var bar)
(binding [bar "bar2"]
(var bar))))
=> true
对我来说,这表明无论是“重新定义”还是动态范围都不会对命名空间限定符号和它所指向的变量之间的关系产生任何更改
现在,我要问一个新问题:
命名空间限定符号是否总是与其所引用的变量同义?
如果这个问题的答案是肯定的,那么我就是不明白为什么会有另一个间接层次
如果答案是否定的,那么我想知道在什么情况下,在同一个程序的一次运行期间,命名空间限定符号将指向不同的变量
总之,我想这是一个很好的问题:主要的好处是它是一个额外的抽象层,在各种情况下都很有用
作为一个具体的例子,符号可以在创建它们所引用的变量之前愉快地存在:
(def my-code `(foo 1 2)) ;; create a list containing symbol user/foo
=> #'user/my-code
my-code ;; confirm my-code contains the symbol user/foo
=> (user/foo 1 2)
(eval my-code) ;; fails because user/foo not bound to a var
=> CompilerException java.lang.RuntimeException: No such var: user/foo...
(def foo +) ;; define user/foo
=> #'user/foo
(eval my-code) ;; now it works!
=> 3
元编程的好处应该很清楚——在需要实例化代码并在完全填充的名称空间中运行之前,您可以构造和操作代码。Clojure是我迄今为止第一个(也是唯一一个)lisp,所以这个答案有点像猜测。尽管如此,clojure网站上的以下讨论似乎是相关的(我的重点):
Clojure是一种实用的语言,它认识到偶尔需要保持对不断变化的值的持久引用,并以受控方式提供了4种不同的机制—变量、引用、代理和原子。Vars提供了一种机制来引用可变存储位置,该存储位置可以在每个线程的基础上动态反弹(到新的存储位置)。每个Var都可以(但不需要)有一个根绑定,这是一个由所有没有每线程绑定的线程共享的绑定。因此,Var的值是其每线程绑定的值,或者,如果它没有绑定在请求该值的线程中,则是根绑定的值(如果有)
因此,将符号间接指向Vars允许线程安全的动态重新绑定(也许可以用其他方式实现,但我不知道)。我认为这是clojure的核心哲学的一部分,即严格而全面地区分身份和状态,以实现健壮的并发
我怀疑,与重新考虑一个不需要特定于线程的动态绑定的问题相比,这个工具很少(如果有的话)提供真正的好处,但如果需要的话,它就在那里。在仔细考虑了这个问题之后,我可以想出几个理由来区分符号和变量,或者像Omri所说的那样,使用它们“将符号映射到其基础值的两个间接层次”。我将把最好的一个保存到最后
1:通过分离“变量”和“可引用变量的标识符”的概念,Clojure使概念更清晰。在CL中,当读者看到a
时,它返回一个符号对象,该对象携带指向顶级绑定的指针,即使a
在当前范围内是本地绑定的。(在这种情况下,计算器将不会使用这些顶级绑定。)在Clojure中,符号只是一个标识符,仅此而已
这与一些海报提出的观点有关,即符号也可以引用Clojure中的Java类。如果符号带有绑定,则在符号引用Java类的上下文中可以忽略这些绑定,但在概念上会很混乱
2:在某些情况下,人们可能希望使用符号作为映射键等。如果符号是可变对象(就像在CL中一样),它们就不适合Clojure的不可变数据结构
3:在(可能很少)符号用作地图键等,甚至可能由API返回的情况下,Clojure符号的相等语义比CL符号更直观。(参见@amalloy的答案。)
4:因为Clojure强调功能公关
(def my-code `(foo 1 2)) ;; create a list containing symbol user/foo
=> #'user/my-code
my-code ;; confirm my-code contains the symbol user/foo
=> (user/foo 1 2)
(eval my-code) ;; fails because user/foo not bound to a var
=> CompilerException java.lang.RuntimeException: No such var: user/foo...
(def foo +) ;; define user/foo
=> #'user/foo
(eval my-code) ;; now it works!
=> 3