在Scheme和Racket中,符号(symbol)类似于一个不可变的字符串,它被内部化(interned),使得可以用eq?
进行比较(快速、基本上是指针比较)。符号和字符串是不同的数据类型。
符号的一个用途是轻量级枚举。例如,你可能会说方向要么是'north
、'south
、'east
或'west
。当然,你可以使用字符串来达到相同的目的,但这样会略微降低效率。使用数字也是不好的选择;应该尽可能以明显且透明的方式表示信息。
另一个例子是SXML,它是使用列表、符号和字符串表示XML的一种表示方法。特别地,字符串代表字符数据,符号则代表元素名称。因此,XML <em>hello world</em>
将由值(list 'em "hello world")
表示,更简洁地写成'(em "hello world")
。
符号的另一个用途是作为键。例如,你可以将方法表实现为将符号映射到实现函数的字典。要调用一个方法,你需要查找对应于方法名称的符号。 Lisp/Scheme/Racket非常容易做到这一点,因为语言已经内置了标识符(语言的语法部分)和符号(语言中的值)之间的对应关系。这种对应关系使得支持宏非常容易,宏实现了用户定义的语法扩展到语言中。例如,可以使用隐含的"方法名称"(一个由类系统定义的语法概念)和符号实现一个类系统作为宏库:
(send obj meth arg1 arg2)
=>
(apply (lookup-method obj 'meth) obj (list arg1 arg2))
(在其他Lisp中,我所说的大部分都是正确的,但还有其他需要了解的事情,比如包和函数与变量卡槽的区别,如果我没记错的话。)
enum
时,请改用符号(且枚举不是特定数字的标签)。例如,enum { north, south, east, west }
可以写成 'north' 'south' 'east' 'west'
。您无需像在C中使用enum
那样“声明”它们。只需直接使用即可。但是,符号不能做到 enum { north = 123 }
。对于这个问题,您需要更像C的#define
:(define north 123)
。 - Greg Hendershott符号是一个具有简单字符串表示的对象,通过默认设置保证进行了内部化(即任何写成相同的两个符号在内存中都是相同的对象)(引用相等)。
为什么Lisp需要符号?这在很大程度上是因为Lisp将其自己的语法嵌入作为语言的数据类型。编译器和解释器使用符号来表示程序中的标识符;由于Lisp允许您将程序的语法表示为数据,因此它提供符号,因为它们是表示的一部分。
它们除此之外还有什么用处?有一些:
(eq (make-symbol "test") (make-symbol "test"))
怎么样?我认为你关于符号保证被内部化的说法仅适用于那些被读取的符号。不过,关于编译器的观点确实很有趣;我从未这样想过 :-) - Sean Allred示例用法:不同的符号可以表示国际象棋棋盘上的不同棋子。
摘自Harold Abelson和Gerald Jay Sussman于1996年出版的《计算机程序的构造与解释第二版》:
为了操作符号,我们需要在我们的语言中引入一个新元素:引用数据对象的能力。假设我们想构建列表 (a b)。我们不能用 (list a b) 来实现这一点,因为这个表达式构建的是 a 和 b 的值而不是它们本身的符号。 在自然语言的语境下,这个问题是众所周知的,单词和句子可以被视为语义实体或字符字符串(句法实体)。在自然语言中通常的做法是使用引号来表示一个单词或句子应该被视为一串字符。例如,“John”的第一个字母显然是“J”。如果我们告诉某人“大声说你的名字”,我们期望听到那个人的名字。但是,如果我们告诉某人“大声说‘你的名字’”,我们期望听到“你的名字”这几个单词。请注意,我们被迫嵌套引号以描述别人可能会说什么。 我们可以采用同样的做法来标识要作为数据对象处理而不是作为要评估的表达式的列表和符号。 然而,我们的引用格式与自然语言的不同之处在于,我们只在要引用的对象开头放置一个引号符号(传统上是单引号符号’)。在Scheme语法中,我们可以依靠空格和括号来分隔对象,因此我们可以采用这种方式。因此,单引号字符的含义是引用下一个对象。 现在我们可以区分符号和它们的值:
(define a 1)
(define b 2)
(list a b)
(1 2)
(list ’a ’b)
(a b)
(list ’a b)
(a 2)
包含符号的列表看起来就像我们语言中的表达式:
(* (+ 23 45) (+ x 9))
(define (fact n) (if (= n 1) 1 (* n (fact (- n 1)))))
例子:符号微分
\``,
,`等)。显式求值可以更好地表达底层的真实情况,尽管对自然语言用户来说可能更不熟悉。 - FrankHB符号只是一个特殊的名称,用于表示一个值。这个值可以是任何东西,但符号用于每次引用相同的值,并且这种方式用于快速比较。正如你所说,如果你有命令式思维,它们就像C语言中的数值常量,通常是这样实现的(内部存储数字)。
eval
与字符串的不同可能会有所帮助。eval
:(define my-eval (lambda (x) (eval x (scheme-report-environment 5))))
(define s 2) ;Value: s
(my-eval "s") ;Value: "s"
(my-eval s) ;Value: 2
(define a '+) ;Value: a
(define b "+") ;Value: b
(my-eval a) ;Value: #[arity-dispatched-procedure 12]
(my-eval b) ;Value: "+"
((my-eval a) 2 3) ;Value: 5
((my-eval b) 2 3) ;ERROR: The object "+" is not applicable.