我总是对Clojure中的Symbols和Vars感到有些困惑。 例如,可以肯定地说+是一个用来表示var的符号,这个var指的是一个可以添加数字的函数吗?
那么当我在REPL中输入“+”时会一步一步地发生什么呢?
答案 0 :(得分:60)
你可以通过引用它来谈论一个符号+:
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>
请注意,词法绑定是一种不同的机制,它们可以隐藏Vars,但您可以通过明确引用Var来绕过它们:
user=> (let [+ -] [(+ 1 2) (@#'+ 1 2)])
[-1 3]
在最后一个例子中,deref甚至可以省略:
user=> (let [+ -] [(+ 1 2) (#'+ 1 2)])
[-1 3]
这是因为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
答案 1 :(得分:7)
请参阅the documentation for namespaces:
命名空间是从简单(非限定)符号到Vars和/或Classes的映射。可以使用def或其任何变体在名称空间中实现Vars,在这种情况下,它们具有名称的简单符号和对其包含名称空间的引用,并且名称空间将该符号映射到相同的var。命名空间还可以包含通过使用引用或使用从符号到在其他命名空间中实现的变量的映射,或者通过使用导入从符号到类对象的映射。
所以基本上你的步骤1和2是统一的:名称空间是符号表。
关于第3步:我喜欢变量的定义,它们是值名称的组合。符号是变量的名称,对其进行评估将产生其值。
答案 2 :(得分:4)
这个答案与其他答案没有什么不同,它只是不假设你原本希望学习几个新的功能和概念,只是为了理解发生了什么:
+
是位于clojure.core
的符号,默认情况下您的代码可以访问该符号。 +
时,
clojure将尝试调用该函数(NullPointerException
如果此Var发生时不指向函数)。如果作为参数提供
另一个函数,该函数也可以这样做来调用它。那是
函数调用是如何工作的。大多数或所有语言都使用符号表。作为一种有点动态的语言,Clojure使用这个额外的间接层(符号→Var→函数,而不仅仅是符号→函数),以便动态地重写哪个函数与哪个符号相关联 - 更加可行和优雅,这有时是一个初学者的好奇心。
由于其他答案有点过分强调,否则您可能会执行花哨的东西,例如引用它('+
)以避免评估,或者甚至用class
检查它/或resolve
好像您有兴趣验证它是什么(class
),或者它位于哪个命名空间(resolve
)。您还可以通过var
或#'
来查看变量点。如果你正在编写宏,或者你非常倾向于实验,你通常会做那些奇特的事情,特别是在repl工作时;根据您编写宏的样式,您可能会在其中引用相当多的内容。
作为一种有点灵活的语言,clojure暴露了api,可以自己使用Symbol→Var→函数。你通常不会只使用一个函数,因为很明显这将是无聊和冗余,但它可以用来说明这个过程:
(deref (resolve '+))
即,首先将符号解析为其Var,然后得到Var指向的东西。这只是说明了到达一个函数(符号→Var→函数)的两步过程,它发生在幕后。我希望你能避免阅读这个额外的部分。
原始问题的答案很简单:是的。
答案 3 :(得分:0)
我发现了解符号、函数、文字和变量之间的区别对于理解正在发生的事情是必要的。
(def one (fn [] 1))
⇒ #'example/one
。它引用了 function #function[example/one]
(def x one)
⇒ #'example/x
引用了函数 #function[example/one]
(def y 'one)
⇒ #'example/y
引用了符号 one
(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:
(type one)
⇒ example$one
(type x)
⇒ example$one
(type y)
⇒ clojure.lang.Symbol
(type z)
⇒ clojure.lang.Var
在上述情况下,值为“is the value of the binding of the global var named by the symbol”。另一个答案更简洁地说:符号 → var → 值。
(one)
⇒ 1
(x)
⇒ 1
(y)
⇒ 不相关的 associative lookup 错误(z)
⇒ 1
记住:#'example/z
引用了 var #'example/one
。 #'example/x
和 #'example/y
都引用 #function[example/one]
。当原始符号是 interned 到一个新函数:(def one (fn [] 2))
时,这种差异的重要性就显现出来了。
(one)
⇒ 2
← 新值(x)
⇒ 1
← 原始值(y)
⇒ 不相关的 associative lookup 错误(z)
⇒ 2
← 新值遵循符号→var→value的逻辑:
x
→ (var x)
→ (defn one [] 1)
x
解析为 (var x)
(“符号命名的全局变量”)(var x)
取消引用 x
的当前绑定:(fn [] 1)
(“由符号命名的全局变量的绑定”)z
→ (var z)
→ (var one)
。
z
解析为它的 var,它取消引用 (var one)
one
解析为它的 var,它取消引用 (fn [] 2)
最后一点。 #'example/one
仍然计算并返回文字(1
或 2
),因为 var 位于列表的第一个位置 ((z)
)。这种行为类似于将关键字放在列表的第一个位置,将地图放在第二个位置:(:two {:one 1 :two 2})
⇒ 2
。
答案 4 :(得分:0)
打电话
(ns-map *ns*)
获取命名空间中所有可用符号的映射以及它们指向的变量