解释Clojure符号

解释Clojure符号,clojure,Clojure,我有一个符号“a”绑定到一个函数: (defn a [] (println "Hello, World")) user=> a #<user$a__292 user$a__292@97eded> user=> (a) Hello, World nil 第二个问题:如果列表中有一个符号,为什么我不能像直接计算符号一样计算它 user=> (def l '(a 1 2)) #'user/l user=> 'l l user=> (firs

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

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

user=> a
#<user$a__292 user$a__292@97eded>
user=> (a)    
Hello, World
nil
第二个问题:如果列表中有一个符号,为什么我不能像直接计算符号一样计算它

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)
我怀疑,在对符号如何工作的基本理解中,我有一个致命的缺陷。上述代码有什么问题?

用户=>(定义l'(a 1 2)) 用户=>((第一个l))

将此转化为:

用户=>(定义l`(~a 1 2))

此处的~将符号a解析为其对应的var,反勾号用于取消引用

一般来说,您必须理解变量(绑定到某个对象)和符号(从不绑定到任何对象)之间的区别

我会尽力解释(希望我的解释不会让你进一步困惑):

->在当前命名空间中的符号“v”(完全限定的用户/v,假设这是当前命名空间)下定义一个var,并将其(var,而不是符号)绑定到对象“content”

->将v解析为var,并获取绑定值

user=> #'v
#'user/v
->解析为var本身

user=> 'v
v
->不解析任何内容,只是一个简单的符号(不幸的是,REPL没有指明这一点,将“v”打印为“v”)

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

user=> '(v)
(v)
->简单的引用,不能解决任何问题

user=> `(v)
(user/v)
->语法引号,与引号相同,但将符号解析为命名空间限定符号

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

->将符号解析为其var(隐式取消引用),生成其绑定对象

使用符号作为函数与对其求值不同。作为函数的符号与作为函数的关键字的工作方式相同。像这样:

user=> (declare a)
#'user/a
user=> (def a-map {'a "value"})
#'user/a-map
user=> ('a a-map)
"value"
user=>
user=> ('x (.getMappings *ns*))
#'user/x
这不是通常使用符号的方式。它们更常用于在命名空间中查找变量,以及在宏中生成代码

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

user=> (def x 1)
#'user/x
使用
def
,我们创建了一个“var”。var的名称是符号user/x。
def
特殊表单将var本身返回给repl,这就是我们可以看到的结果。让我们试着抓住这个变量:

user=> #'x
#'user/x
#'
语法是一个读卡器宏,表示“给我以下符号所指的变量”。在我们的例子中,该符号是“x”。我们得到了和以前一样的var。变量是指向值的指针,可以取消引用:

user=> (deref #'x)
1
但需要先找到var,然后才能对其进行解引用。这就是符号的可调用性发挥作用的地方。名称空间就像一个映射,其中符号是键,变量是值,当我们简单地命名一个符号时,我们在名称空间中隐式地查找它的变量。像这样:

user=> (declare a)
#'user/a
user=> (def a-map {'a "value"})
#'user/a-map
user=> ('a a-map)
"value"
user=>
user=> ('x (.getMappings *ns*))
#'user/x
尽管在现实中,它可能更像这样:

user=> (.findInternedVar *ns* 'x)
#'user/x
user> ('a {'a :foo})
:foo
现在,我们在这个未加引号的符号的旅程中绕了一圈:

user=> (deref (.findInternedVar *ns* 'x))
1
user=> x
1
不过,这两者并不完全平等。因为计算器对所有符号都执行此操作,包括
deref
和*ns*

关于引用的问题是,你基本上绕过了整个机制,只需要把简单的符号取回来。与
#'
读卡器宏返回普通变量类似,“和”读卡器宏将返回普通符号,分别带有或不带有名称空间限定:

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

REPL=读取评估打印循环。逐步完成读取评估过程

阅读:Clojure看到字符串
“(`a)”
,对其进行解析,最后得到一个数据结构。在读取时,读卡器宏被展开,其他事情不会发生。在这种情况下,读取器展开后引号并以以下内容结束:

user> (read-string "(`a)")
((quote user/a))
EVAL:Clojure尝试评估此对象。评估规则因您所查看的对象类型而异

  • 有些对象本身进行计算(数字、字符串、关键字等)
  • 通过在某个命名空间中解析符号以获得某个值(通常)来计算符号
  • 通过宏展开列表直到没有宏为止,然后递归计算列表中的第一项以获得一些结果值,然后使用列表中第一项的值来决定要执行的操作,可以对列表进行评估。如果第一个值是一种特殊形式,则会发生特殊情况。否则,第一个值将被视为一个函数,并使用列表其余部分的值(通过递归计算列表的所有项获得)作为参数进行调用
  • 等等
请参阅clojure源代码中的
clojure.lang.Compiler/analyzeSeq
以查看列表的计算规则,或
clojure.lang.Compiler/analyzeSymbol
以查看符号。还有很多其他的评估规则

例子 假设您这样做:

user> (user/a)
REPL最终在内部执行此操作:

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

Clojure计算列表中的第一项,它本身就是一个列表

user> (eval '(quote user/a))
user/a
它计算此子列表中的第一项,
quote
,这是一种特殊形式,因此应用特殊规则,并返回未计算的参数(符号
a

符号
a
是本例中的值,因为
fn
是上面的值。因此Clojure将符号本身视为一个函数并调用它。在Clojure中,任何实现
Ifn
接口的东西都可以像
fn
一样调用。恰好
clojure.lang.Symbol
实现了
Ifn
。作为函数调用的符号需要一个参数,一个集合,它在该集合中查找自身。它的用途如下:

user=> (.findInternedVar *ns* 'x)
#'user/x
user> ('a {'a :foo})
:foo
这就是它在这里试图做的。但是您没有传递任何参数,因此会出现错误“传递给:Symbol的参数数量错误”(它需要一个集合)

为了你的
user> (eval '(quote user/a))
user/a
user> ('a {'a :foo})
:foo
user> (eval '((eval (quote user/a))))
Hello, world
user> ((eval (first l)))
Hello, world