解释Clojure符号

时间:2009-07-24 05:58:07

标签: clojure

我有一个符号"a"绑定到一个函数:

(defn a []
    (println "Hello, World"))

user=> a
#<user$a__292 user$a__292@97eded>
user=> (a)    
Hello, World
nil

然后我使用语法引用,它根据Clojure documentation“解析当前上下文中的符号,产生一个完全限定的符号”。但为什么我不能像不合格的符号一样使用它?

user=> `a
user/a
user=> (`a)
java.lang.IllegalArgumentException: Wrong number of args passed to: Symbol (NO_SOURCE_FILE:0)

第二个问题:如果我在列表中有符号,为什么我不能像直接评估符号一样评估它?

user=> (def l '(a 1 2))
#'user/l
user=> 'l
l
user=> (first l)
a
user=> ((first l))
java.lang.IllegalArgumentException: Wrong number of args passed to: Symbol (NO_SOURCE_FILE:0)

我怀疑在某些地方对符号如何在这里起作用的基本理解中存在致命缺陷。上面的代码出了什么问题?

3 个答案:

答案 0 :(得分:36)

REPL =读取eval打印循环。逐步完成read-eval过程。

READ:Clojure看到字符串"(`a)",解析它并最终得到一个数据结构。在读取时,读取器宏被扩展,并且没有太多其他事情发生。在这种情况下,读者会扩展反引号并最终得到:

user> (read-string "(`a)")
((quote user/a))

EVAL:Clojure试图评估这个对象。评估规则取决于您正在查看的对象类型。

  • 有些对象会自行评估(数字,字符串,关键字等)。
  • 通过在某个命名空间中解析符号来评估符号,以获得某些值(通常)。
  • 通过宏扩展列表来评估列表,直到没有剩下的宏,然后递归计算列表中的第一项以获得一些结果值,然后使用值< / strong>列表中的第一项决定要做什么。如果第一个值是特殊形式,则会发生特殊情况。否则,第一个值将被视为一个函数,并使用列表其余部分的值(通过递归计算所有列表的项目获得)作为参数进行调用。

请参阅Clojure来源中的clojure.lang.Compiler/analyzeSeq以查看列表的评估规则,或查看符号的clojure.lang.Compiler/analyzeSymbol。那里有很多其他的评估规则。

实施例

假设你这样做:

user> (user/a)

REPL最终在内部执行此操作:

user> (eval '(user/a))

Clojure看到您正在评估列表,因此它会评估列表中的所有项目。第一个(也是唯一的)项目:

user> (eval 'user/a)
#<user$a__1811 user$a__1811@82c23d>

a不是特殊表单,此列表不需要进行宏扩展,因此在名称空间a结果值中查找符号user 这是一个fn。所以这个fn被调用。

您的代码

但你有这个:

user> (eval '((quote user/a)))

Clojure评估列表中的第一项,它本身就是一个列表。

user> (eval '(quote user/a))
user/a

它评估了此子列表中的第一个项quote,这是一个特殊形式,因此应用特殊规则并返回其参数(符号a)未评估。

在这种情况下,符号a,因为fn是上面的值。所以Clojure将Symbol本身视为一个函数并将其调用。在Clojure中,任何实现Ifn接口的东西都可以像fn一样调用。碰巧clojure.lang.Symbol实现Ifn。称为函数的符号需要一个参数,一个集合,并且它在该集合中查找自己。这意味着像这样使用:

user> ('a {'a :foo})
:foo

这是它试图在这里做的。但是你没有传递任何参数,所以你得到的错误是“传递给:符号的args数量错误”(它需要一个集合)。

要使代码正常工作,您需要两个eval级别。这有效,希望你能明白为什么:

user> (eval '((eval (quote user/a))))
Hello, world
user> ((eval (first l)))
Hello, world

请注意,在实际代码中,直接使用eval通常是一个非常糟糕的主意。到目前为止,宏是一个更好的主意。我只是在这里用它来演示。

查看Clojure源代码中的Compiler.java,了解这一切是如何发挥作用的。这并不难理解。

答案 1 :(得分:15)

使用符号作为函数与评估函数不同。符号作为函数的作用与关键字作为函数的作用相同。像这样:

user=> (declare a)
#'user/a
user=> (def a-map {'a "value"})
#'user/a-map
user=> ('a a-map)
"value"
user=>

这不是您通常使用符号的方式。它们更常用于在命名空间中查找变量,以及在宏中生成代码时。

要分解间接层,让我们将“x”定义为1,看看会发生什么:

user=> (def x 1)
#'user/x

使用def,我们创建了一个“var”。 var的名称是符号user / x。 def特殊表单将var本身返回给repl,这就是我们可以看到的打印内容。让我们试着抓住那个var:

user=> #'x
#'user/x

#'语法是一个读者宏,上面写着“给我以下符号引用的var”。在我们的例子中,该符号是“x”。我们和以前一样恢复了相同的var。 Vars是指向值的指针,可以解除引用:

user=> (deref #'x)
1

但是在可以取消引用之前需要找到var。这就是符号的可调性发挥作用的地方。命名空间就像一个映射,其中符号是键,vars是值,当我们明确地命名符号时,我们隐式地在我们的命名空间中查找它的var。像这样:

user=> ('x (.getMappings *ns*))
#'user/x

虽然实际上可能更像是这样:

user=> (.findInternedVar *ns* 'x)
#'user/x

现在我们已经完成了一个没有引号的符号的旅程:

user=> (deref (.findInternedVar *ns* 'x))
1
user=> x
1
但是,这两者并不完全相同。因为评估者对所有符号都这样做,包括deref和* ns *。

关于引用的事情是你基本上绕过了整个机制,然后只返回普通符号。就像#'阅读器宏得到简单的变量一样,`和'阅读器宏将返回普通符号,分别有或没有名称空间限定:

user=> 'x
x
user=> `x
user/x

答案 2 :(得分:7)

  

用户=&GT; (def l'(a 1 2))   用户=&GT; ((第一个l))

将其转换为:

  

用户=&GT; (def l`(~a 1 2))

〜这里将符号a解析为相应的var,反引号使得取消引用。

一般来说,你必须理解vars(绑定到某些东西)和符号(它们从不绑定到任何东西)之间的区别。

我会尝试解释它(希望我的出版物不会让你更进一步混淆):

user=> (def v "content")
#'user/content

- &GT;在符号'v(完全限定'user / v,假设这是当前命名空间)下定义当前命名空间中的var,并将它(var,而不是符号)绑定到对象“content”。

user=> v
"content"

- &GT;将v解析为var,并获取绑定值

user=> #'v
#'user/v

- &GT;解析为var本身

user=> 'v
v

- &GT;不解决任何问题,只是一个简单的符号(不幸的是,REPL没有表明这一点,打印'v为v)

user=> `v
user/v

- &GT;正如您已经引用的那样,解析为当前上下文(命名空间)中的符号,但结果仍然是符号(完全限定),而不是var user / v

user=> '(v)
(v)

- &GT;简单引用,不解决任何问题

user=> `(v)
(user/v)

- &GT;语法引用,与引用相同,但将符号解析为名称空间限定符号

user=> `(~v)
("content")

- &GT;将符号解析为其var(隐含地取消引用),产生其绑定对象