【问题标题】:Difference between Symbols and Vars in ClojureClojure 中符号和变量的区别
【发布时间】:2012-02-25 04:04:42
【问题描述】:

我总是对 Clojure 中的符号和变量有点困惑。 例如,是否可以说 + 是用于表示 var 的符号,而该 var 指向的值是可以添加数字的函数?

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

  1. 符号被限定为命名空间,在本例中为 clojure.core
  2. 然后在一些符号表中有 + 引用 var 的信息
  3. 当计算这个 var 时,结果是一个函数值?

【问题讨论】:

  • 你似乎很明白这一点:)

标签: clojure


【解决方案1】:

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

user=> '+
+
user=> (class '+)
clojure.lang.Symbol
user=> (resolve '+)
#'clojure.core/+

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

user=> (class #'+)
clojure.lang.Var

Var 引用函数对象:

user=> (deref #'+)
#<core$_PLUS_ clojure.core$_PLUS_@55a7b0bf>
user=> @#'+
#<core$_PLUS_ clojure.core$_PLUS_@55a7b0bf>

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

user=> +
#<core$_PLUS_ clojure.core$_PLUS_@55a7b0bf>

请注意,词法绑定是一种不同的机制,它们可以影响 Var,但您可以通过显式引用 Var 来绕过它们:

user=> (let [+ -] [(+ 1 2) (@#'+ 1 2)])
[-1 3]

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

user=> (let [+ -] [(+ 1 2) (#'+ 1 2)])
[-1 3]

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

使用 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

【讨论】:

  • 添加到这个出色的答案:您还可以限定符号以绕过 let 阴影。 (let [+ -] [(+ 1 1) (clojure.core/+ 1 1)]) =&gt; [0 2]。这表明符号在 Var 解析之前没有被限定。
  • @kotarak 关于限定符号与 let 绑定的符号不同的好点,但我不明白它与 Var 分辨率有什么关系。需要详细说明吗?
  • @jouni-k-seppanen 这与问题中的第一个要点有关。我认为你很好地解决了所有其他问题,但这一点没有评论。 (或者至少在开始时非常隐含。)
  • 很好的答案,但很难理解“解析”、“引用”和“取消引用”之间的语义和区别...
【解决方案2】:

the documentation for namespaces:

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

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

关于第 3 步:我喜欢变量的定义,即它们是值名称的组合。符号是变量的名称,计算它会得到它的值。

【讨论】:

    【解决方案3】:

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

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

    进一步汇总:

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

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

    还有一个花哨的插图供探索者使用:

    作为一种有点灵活的语言,clojure 公开了 api,用于自行获取 Symbol → Var → 函数 walk。你通常不会因为仅仅使用一个函数而这样做,因为显然这会很无聊和多余,但它可以在这里用来说明这个过程:

    (deref (resolve '+))
    

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


    TL;DR

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

    【讨论】:

    • 这真的很有帮助,尤其是我应该阅读的部分。散步有助于理解三部分链。
    【解决方案4】:

    我发现了解符号、函数、文字和变量之间的区别对于了解正在发生的事情是必要的。

    1. (def one (fn [] 1))#'example/one。它引用了 函数 #function[example/one]
    2. (def x one)#'example/x 引用 函数 #function[example/one]
    3. (def y 'one)#'example/y 引用 符号 one
    4. (def z #'one)#'example/z 引用 var #'example/one

    准确地说one 是一个符号,它解析var #'example/one。 var 引用一个函数#function[example/one],它返回文字1

    每个def yields a var。 var 由#'example/x 中的#' 语法表示。每个 var 引用一个值。

    根据Clojure's own documentation,符号解析为一个值、一个特殊的形式,或者在评估时出现一个错误。所以它可能有点令人困惑,因为没有提到 var:

    1. (type one)example$one
    2. (type x)example$one
    3. (type y)clojure.lang.Symbol
    4. (type z)clojure.lang.Var

    在上述情况下,值为“is the value of the binding of the global var named by the symbol”。正如另一个更简洁的答案所说:symbol → var → value。

    1. (one)1
    2. (x)1
    3. (y) ⇒ 不相关 associative lookup 错误
    4. (z)1

    记住:#'example/z 引用 var #'example/one#'example/x#'example/y 均引用 #function[example/one]。当原始符号interned 转换为新函数(def one (fn [] 2)) 时,这种差异的意义就显现出来了。

    1. (one)2 ← 新值
    2. (x)1原始值
    3. (y) ⇒ 不相关 associative lookup 错误
    4. (z)2新值

    按照symbol→var→value的逻辑:

    • x(var x)(defn one [] 1)
      1. x 解析为 (var x)(“由符号命名的全局变量”)
      2. (var x) dereferences x 的当前绑定:(fn [] 1)(“由符号命名的全局变量的绑定”)
    • z(var z)(var one)
      1. z 解析为它的 var,它取消引用 (var one)
      2. one 解析为它的 var,它取消引用 (fn [] 2)

    最后一点。 #'example/one 仍然计算并返回一个文字(12),因为 var 位于列表的第一个位置((z))。这种行为类似于将关键字放在列表的第一个位置,将地图放在第二个位置:(:two {:one 1 :two 2})2

    【讨论】:

      【解决方案5】:

      打电话

      (ns-map *ns*)
      

      获取命名空间中所有可用符号的映射以及它们指向的变量

      【讨论】:

      • 其实是ns-map
      猜你喜欢
      • 2012-07-24
      • 2010-10-11
      • 1970-01-01
      • 2011-04-05
      • 1970-01-01
      • 2014-09-16
      • 2013-07-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多