Lisp/Scheme中的符号是什么?

60
我至今仍然不明白符号“iamasymbol”的目的。我理解数字、布尔值、字符串、变量,但符号对于我这种习惯于命令式思维的人来说有点难以理解。究竟该如何使用它们?在程序中应该如何使用它们?我对这个概念的理解非常有限。

5
这些东西中有一件不同。你提到了“数字”、“布尔值”、“字符串”,它们都是数据类型。符号也属于这个范畴。变量则不是。 - dyoo
1
值得注意的是,Ruby也有符号。:parent就是一个符号。你可以把它看作是枚举或者内部字符串。它保留了它的名称(有助于理解),但不是String类型(除非你将其转换)。 - ccoakley
1
https://en.wikipedia.org/wiki/Symbol_(programming) - Jiacai Liu
它们是在构建/编译时用于数字偏移量的占位符。生成的代码将使用相对于二进制文件加载位置的RAM地址。这基本上是为什么他们首先制作编译器的全部原因,“user->active = false”比“0x7e62af80 + 0x3e”方便得多;) - Christoffer Bubach
6个回答

44

在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中,我所说的大部分都是正确的,但还有其他需要了解的事情,比如包和函数与变量卡槽的区别,如果我没记错的话。)


1
注意:几个月前,许多其他Racket值也被内部化了,例如出现为文字值的字符串、inexacts和regexps。 - John Clements
4
如果您来自C或C#(仅从您的用户名猜测,不懂.NET):在这些语言中使用enum时,请改用符号(且枚举不是特定数字的标签)。例如,enum { north, south, east, west } 可以写成 'north' 'south' 'east' 'west'。您无需像在C中使用enum那样“声明”它们。只需直接使用即可。但是,符号不能做到 enum { north = 123 }。对于这个问题,您需要更像C的#define(define north 123) - Greg Hendershott

39

符号是一个具有简单字符串表示的对象,通过默认设置保证进行了内部化(即任何写成相同的两个符号在内存中都是相同的对象)(引用相等)。

为什么Lisp需要符号?这在很大程度上是因为Lisp将其自己的语法嵌入作为语言的数据类型。编译器和解释器使用符号来表示程序中的标识符;由于Lisp允许您将程序的语法表示为数据,因此它提供符号,因为它们是表示的一部分。

它们除此之外还有什么用处?有一些:

  • Lisp通常用于实现嵌入式领域特定语言。其中许多技术来自编译器领域,因此符号在这里是一个有用的工具。
  • Common Lisp中的宏通常涉及更详细地处理符号。(尽管特别是为宏扩展生成唯一标识符需要能够生成保证不等于任何其他标识符的符号。)
  • 固定的枚举类型最好使用符号而不是字符串来实现,因为符号可以通过引用相等进行比较。
  • 您可以构建许多数据结构,在使用符号和引用相等性时可以获得性能优势。

2
(eq (make-symbol "test") (make-symbol "test"))怎么样?我认为你关于符号保证被内部化的说法仅适用于那些被读取的符号。不过,关于编译器的观点确实很有趣;我从未这样想过 :-) - Sean Allred

11

示例用法:不同的符号可以表示国际象棋棋盘上的不同棋子。


10

摘自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

4

符号只是一个特殊的名称,用于表示一个值。这个值可以是任何东西,但符号用于每次引用相同的值,并且这种方式用于快速比较。正如你所说,如果你有命令式思维,它们就像C语言中的数值常量,通常是这样实现的(内部存储数字)。


2
特别地,可以使用(eq?sym1 sym2)在常数时间内检查两个符号是否相等。 符号不是“Scheme”或“Lisp”概念:它们是通用的。 想想术语“符号表”。 (http://en.wikipedia.org/wiki/Symbol_table) Scheme或Racket语言中的符号只是公开了这个值类似于字符串但具有快速相等性检查的额外属性的概念。 - dyoo

1
为了阐述Luis Casillas所说的观点,观察符号eval与字符串的不同可能会有所帮助。
下面的示例适用于mit-scheme(Release 10.1.10)。为方便起见,我将此函数称为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.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接