为什么Clojure区分符号和变量?

Ale*_*x D 35 lisp symbols clojure

我已经看到了这个问题,但它没有解释我在想什么.

当我第一次从Common Lisp来到Clojure时,我很困惑为什么它将符号和关键字视为单独的类型,但后来我想出来了,现在我觉得这是个好主意.现在我想弄清楚为什么符号和变量是单独的对象.

据我所知,Common Lisp实现通常使用一个结构代表一个"符号",该结构具有1)名称的字符串,2)在函数调用位置计算时指向符号值的指针,3)指向其值时的指针评估外部呼叫位置,以及4)财产清单等.

忽略Lisp-1/Lisp-2的区别,事实仍然是在CL中,"符号"对象直接指向其值.换句话说,CL结合了Clojure在单个对象中称为"符号"和"var"的内容.

在Clojure中,要评估符号,首先必须查找相应的var,然后必须取消引用var.为什么Clojure以这种方式工作?这样的设计可能会带来什么好处?我理解vars有一些特殊属性(它们可以是private,或const,或动态......),但这些属性不能简单地应用于符号本身吗?

lev*_*and 50

其他问题涉及符号的许多真实方面,但我会尝试从另一个角度解释它.

符号是名称

与大多数编程语言不同,Clojure区分事物事物名称.在大多数语言中,如果我说出类似的话var x = 1,那么说"x是1"或"x的值是1"是正确和完整的.但是在Clojure中,如果我说(def x 1),我已经完成了件事:我创建了一个Var(一个值保持实体),并且我用符号命名了x.说"x的值是1"并不能说明Clojure中的整个故事.更准确(尽管很麻烦)的陈述是"由符号x命名的var的值是1".

符号本身只是名称,而变量是携带价值的实体,而且它们本身没有名称.如果扩展前面的例子并说(def y x),我还没有创建一个新的var,我只是给了我现有的var第二个名字.两个符号xy用于相同变种,其中具有1的值这两个名字.

一个类比:我的名字是"卢克",但这与我不一样,我是谁.这只是一个词.在某些时候我可以改变我的名字并不是不可能的,还有许多其他人与我的名字相同.但在我的朋友圈(在我的名字空间,如果你愿意)的背景下,"卢克"这个词意味着我.在一个幻想的Clojure-land中,我可能是一个为你带来价值的var.

但为什么?

那么为什么这个额外的名称概念与变量不同,而不是将两者混为大多数语言呢?

首先,并非所有符号都与vars绑定.在本地上下文中,例如函数参数或let绑定,代码中的符号引用的值实际上根本不是var - 它只是一个本地绑定,它将被优化掉并在它到达时转换为原始字节码编译器.

但最重要的是,它是Clojure"代码就是数据"哲学的一部分.的代码行(def x 1)不只是一个表达式,它也是数据,具体包括的值的列表def,x1.这非常重要,特别是对于将代码作为数据进行操作的宏.

但是,如果 (def x 1)是列表,那么列表中的值是什么?特别是,这些价值观的类型是什么?显然1是一个数字.但是,我们defx?当我将它们作为数据操作时,它们的类型是什么?答案当然是符号.

这就是符号在Clojure中是一个独特实体的主要原因.在某些上下文中,例如宏,您希望获取名称并对其进行操作,与运行时或编译器授予的任何特定含义或绑定脱节.名字必须是某种东西,它们就是符号.

  • `如果扩展前面的示例并说 (def yx),我还没有创建新的 var,我只是给了现有的 var 第二个名称。` -- 这是不正确的;x 和 y 是两个不同的变量,而不是一个具有两个名称的变量。 (2认同)

Ale*_*x D 10

在仔细考虑这个问题之后,我可以想到区分符号和变量的几个原因,或者正如Omri所说的那样,使用"两个层次的间接来将符号映射到它们的基础值".我将保留最好的一个...

1:通过分离"变量"和"可以引用变量的标识符"的概念,Clojure在概念上使事情变得更清晰.在CL中,当读者看到时a,它返回一个符号对象,该对象带有指向顶级绑定的指针,即使它 a 是在当前作用域中本地绑定的.(在这种情况下,求值程序不会使用那些顶级绑定.)在Clojure中,符号只是一个标识符,仅此而已.

这连接到一些海报制作的点,符号也可以引用Clojure中的Java类.如果符号与它们进行绑定,那么这些绑定只能在符号引用Java类的上下文中被忽略,但在概念上它将是混乱的.

2:在某些情况下,人们可能希望使用符号作为地图键等.如果符号是可变对象(因为它们在CL中),它们将不适合Clojure的不可变数据结构.

3:在(可能很少见)将符号用作映射键等的情况下,甚至可能由API返回,Clojure符号的等式语义比CL的符号更直观.(见@ amalloy的回答.)

4:由于强调的Clojure函数式编程,很多工作都做完了使用类似的高阶函数partial,comp,juxt,等等.即使你没有使用它们,你仍然可以将函数作为自己函数的参数等.

现在,当你传递my-func给高阶函数,它并没有保留被称为"我的-FUNC"变量任何引用.它只是捕捉现在的价值.如果my-func稍后重新定义,则更改将不会"传播"到使用值定义的其他实体my-func.

即使在这种情况下,通过使用#'my-func,您也可以显式请求每次调用派生函数my-func应该查找当前值.(大概以牺牲性能为代价.)

在CL或Scheme中,如果我需要这种间接,我可以想象将一个函数对象存储在一个cons或vector或struct中,并在每次调用它时从那里检索它.实际上,任何时候我需要一个可以在代码的不同部分之间共享的"可变引用"对象,我可以使用cons或其他可变结构.但在Clojure中,列表/矢量/等.是所有不变的,所以你需要一些方法来明确提及"一些东西,是可变的."


ama*_*loy 6

(ns a)

(defn foo [] 'foo)
(prn (foo))


(ns b)

(defn foo [] 'foo))
(prn (foo))
Run Code Online (Sandbox Code Playgroud)

符号foo是在两种情况下完全相同的符号(即,(= 'foo (a/foo) (b/foo))为真),但在两个上下文它需要携带不同的值(在这种情况下,指针的两个功能之一).


Omr*_*ein 6

我已经从你的帖子中推测了以下问题(告诉我,如果我不在基础上):
为什么将符号映射到它们的基础值有两个级别的间接?

当我第一次回答这个问题时,过了一段时间我想出了两个可能的原因:动态"重新定义",以及相关的动态范围概念.但是,以下使我确信这些都不是这种双重间接的原因:

=> (identical? (def a 0) (def a 10))
=> true

=> (declare ^:dynamic bar)
=> (binding [bar "bar1"]
     (identical? (var bar)
                 (binding [bar "bar2"]
                   (var bar))))
=> true
Run Code Online (Sandbox Code Playgroud)

对我来说,这表明"重新定义"和动态范围都不会对命名空间限定符号与它指向的var之间的关系产生任何改变.

此时,我将问一个新问题:
名称空间限定符号是否始终与它引用的var同义?

如果这个问题的答案是肯定的,那么我根本就不明白为什么应该有另一层次的间接.

如果答案是否定的,那么我想知道在同一程序的单次运行期间,在什么情况下命名空间限定符号将指向不同的变量.

总而言之,我想是一个很好的问题:P


mik*_*era 5

主要的好处是它是一个额外的抽象层,在各种情况下都很有用.

作为一个具体的例子,符号可以在创建它们引用的var之前愉快地存在:

(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
Run Code Online (Sandbox Code Playgroud)

元编程方面的好处应该是明确的 - 您可以在需要实例化之前构造和操作代码,并在完全填充的命名空间中运行它.

  • 与CL没有区别这里:CL-USER>(defvar my-code`(foo 1 2))MY-CODE CL-USER> my-code(FOO 1 2)CL-USER>(eval my-code); 在:FOO 1; (FOO 1 2); ; 陷入STYLE-WARNING:; 未定义的功能:FOO; ; 编制单位完成; 未定义的功能:; FOO; 遇到1个STYLE-WARNING条件; 评估在#<UNDEFINED-FUNCTION FOO {10174E4A93}>上中止.CL-USER>(defun foo(ab)(+ ab))FOO CL-USER>(eval my-code)3 (4认同)

归档时间:

查看次数:

2719 次

最近记录:

7 年,10 月 前