Clojure中符号与变量的区别

Mic*_*ent 44 clojure

我总是对Clojure中的Symbols和Vars感到困惑.例如,可以肯定地说+是用于表示var的符号,并且此var指向一个可以添加数字的函数的值吗?

那么当我在REPL中输入"+"时,会一步一步地发生什么?

  1. 符号被限定为命名空间,在本例中为clojure.core
  2. 然后在一些符号表中有+指向var的信息
  3. 评估此var时,结果是函数值?

Jou*_*nen 63

有一个符号+,你可以通过引用它来谈论它:

user=> '+
+
user=> (class '+)
clojure.lang.Symbol
user=> (resolve '+)
#'clojure.core/+
Run Code Online (Sandbox Code Playgroud)

所以它解析为#'+,这是一个Var:

user=> (class #'+)
clojure.lang.Var
Run Code Online (Sandbox Code Playgroud)

Var引用了函数对象:

user=> (deref #'+)
#<core$_PLUS_ clojure.core$_PLUS_@55a7b0bf>
user=> @#'+
#<core$_PLUS_ clojure.core$_PLUS_@55a7b0bf>
Run Code Online (Sandbox Code Playgroud)

(@符号只是deref的简写.)当然,获取该函数的常用方法是不引用符号:

user=> +
#<core$_PLUS_ clojure.core$_PLUS_@55a7b0bf>
Run Code Online (Sandbox Code Playgroud)

请注意,词法绑定是一种不同的机制,它们可以隐藏Vars,但您可以通过明确引用Var来绕过它们:

user=> (let [+ -] [(+ 1 2) (@#'+ 1 2)])
[-1 3]
Run Code Online (Sandbox Code Playgroud)

在最后一个例子中,deref甚至可以省略:

user=> (let [+ -] [(+ 1 2) (#'+ 1 2)])
[-1 3]
Run Code Online (Sandbox Code Playgroud)

这是因为Var通过调用自身的deref,将结果转换为IFn并将函数调用委托给它来实现IFn(Clojure函数的接口).

使用defn定义私有函数时使用的可见性机制基于符号上的元数据.您可以通过直接引用Var来绕过它,如上所述:

user=> (ns foo)
nil
foo=> (defn- private-function [] :secret)
#'foo/private-function
foo=> (in-ns 'user)
#<Namespace user>
user=> (foo/private-function)
java.lang.IllegalStateException: var: #'foo/private-function is not public (NO_SOURCE_FILE:36)
user=> (#'foo/private-function)
:secret
Run Code Online (Sandbox Code Playgroud)

  • 除了这个优秀的答案:您还可以限定符号以绕过"让"阴影.`(让[+ - ] [(+ 1 1)(clojure.core/+ 1 1)])=> [0 2]`.这表明在Var解析之前符号不合格. (4认同)

Rör*_*örd 8

请参阅命名空间的文档:

命名空间是从简单(非限定)符号到Vars和/或类的映射.可以使用def或其任何变体在名称空间中实现Vars,在这种情况下,它们具有名称的简单符号和对其包含名称空间的引用,并且名称空间将该符号映射到相同的var.命名空间还可以包含通过使用引用或使用从符号到在其他命名空间中实现的变量的映射,或者通过使用导入从符号到类对象的映射.

所以基本上你的步骤1和2是统一的:名称空间是符号表.

关于第3步:我喜欢变量的定义,它们是值名称的组合.符号是变量的名称,对其进行评估将产生其值.


mat*_*ter 8

这个答案与其他答案没有太大区别,它只是不假设您最初希望学习几个新功能和概念只是为了了解正在发生的事情:

  1. +是一个符号clojure.core,默认情况下您的代码可以访问它。
  2. 当在您的代码中使用时没有任何非常高级的意图,例如引用它或找出它的类?clojure 将寻找它指向的 Var。
  3. 如果这个 Var 是一个函数,当+它用于列表的头部位置时,clojure 将尝试调用该函数(NullPointerException如果这个 Var 恰好没有指向一个函数)。如果作为参数提供给另一个函数,该函数也可以调用它。这就是函数调用的工作原理。

进一步的评论:

大多数或所有语言都使用符号表。作为一种有点动态的语言,Clojure 使用这个额外的间接层(Symbol ? Var ? 函数,而不仅仅是 Symbol ? 函数),以便动态重写哪个函数与哪个符号相关联?更可行和优雅,这有时是初学者好奇的来源。

由于其他答案有些过分强调,否则您可能会执行诸如引用它 ( ) 之类的花哨的东西'+来避免对其进行评估,或者甚至检查它class和/或resolve好像您有兴趣验证它是什么 ( class) 或它所在的命名空间在 ( resolve) 您还可以查看它指向的 varvar#'。如果您正在编写宏或者您非常喜欢实验,您通常会做那些奇特的事情,尤其是在 repl 中工作时;根据您编写宏的风格,您实际上可能会在其中引用很多内容。

以及为超乎想象的人准备的精美插图:

作为一种有点灵活的语言,clojure 公开了用于获取 Symbol 的 api ?无功?功能自行行走。您通常不会仅使用函数就这样做,因为显然这会很无聊且多余,但可以在此处使用它来说明该过程:

(deref (resolve '+))
Run Code Online (Sandbox Code Playgroud)

即,符号首先被解析为它的 Var,然后是 Var 所指向的东西。这只是说明了到达函数(Symbol ? Var ? 函数)的两步过程,它发生在幕后。我希望你避免阅读这个额外的部分。


TL; 博士

原始问题的答案很简单:是的。