符号和名称之间有什么区别?

时间:2019-08-04 18:05:27

标签: scheme lisp racket

方案编程语言中

  

方案读取器(由get-datum和read调用)和过程   内部符号表中的字符串->符号目录符号,并始终返回相同的符号   每当遇到相同的名称时。

内部符号表”中的键和值是什么?

“遇到相同的名称时,总是返回相同的符号”是否意味着符号及其名称是两个不同的概念?

string->symbolsymbol->string可以将符号与什么区别?他们是否将符号与符号名称(以字符串形式)区分开?

同一本书说

  

关键字,变量和符号统称为标识符

标识符和名称是否具有相同的概念?如果是,符号和名称之间有什么区别?

2 个答案:

答案 0 :(得分:3)

在最简单的级别上,符号是具有身份的名称。这就是您希望在任何编程语言中使用的标识符的目的:如果在编译器查看某些代码时,它看到某个变量名foo,则需要知道它与相同,程序中其他位置的其他名称foo(当然要受该语言的范围和范围规则的约束)。在实现上,这意味着将符号 interned 插入某种表中,以便您可以回答诸如“这东西和那东西一样吗?”之类的问题。和“我已经看过这个东西了吗?”很快特别是在您拥有一系列决定要作为符号名称的字符的那一刻,然后您在表格中查找该名称,如果有,则使用现有的符号,如果使用不是创建一个新的符号,将其存储在表中,然后将其返回。

大量的编程语言实现使用某种符号结构和一个或多个符号表(链接的过程实质上是确定符号对实际上是相同符号的过程)。例如,因此库中的事物(以符号命名)可以与对其的引用(以符号命名)相关联。

但是想要谈论编程语言的编程语言 通常在语言级别将符号公开为对象,因为这显然很有用。 Lisp和Scheme(或“ Lisp家庭语言”,或者您想将其分解)是与编程语言有关的编程语言的典型示例之一。

我对计划标准的了解不如我应该熟悉(我是球拍和CL的人员),但是:

  • string->symbol接受一个字符串,并返回一个名称可能是新构造的符号,该符号已被 interned 调用,每次使用相同的字符串调用该符号时,都会返回相同的符号字符顺序;
  • symbol->string带有一个符号,并告诉您其名称,它是一个字符串。

尤其是这种情况

(eq? (string->symbol "foo") (string->symbol "foo"))

是真的。甚至更强大

(eq? (string->symbol (make-string 3 #\f))
     (string->symbol (make-string 3 #\f)))

是真的。但是

(eq? (make-string 3 #\f)
     (make-string 3 #\f))

false ,因为make-string产生了一个新字符串,因为它是新字符串,所以它与其他任何字符串(或任何其他对象)都不是eq?

因此,string->symbol是您要实现的一种语言,它是在您要使用一系列字符来命名符号的时候。

symbol->string是获取符号名称的方式:如果要打印它,这就是您要调用的名称。通常没有定义(我认为实际上没有定义)是否

(let ((s (string->symbol "s")))
  (eq? (symbol->string s)
       (symbol->string s)))

是对还是错:symbol->string可能返回实际上是 符号名称的字符串,也可能返回它的副本(并且不需要“实际上是名称的字符串”实际上存在于任何地方:例如,符号所在的表可能是一个特里树,其中不存储在任何地方命名它们的字符串。

情况将会如此

(let ((s (string->symbol "s")))
  (string=? (symbol->string s)
            (symbol->string s)))

是真的。

因此回答您的问题:是的,符号及其名称是不同的概念,不同之处在于 identity 的概念:如果您有两个符号,则可以知道它们是否实际上是通过将它们与eq?进行比较来获得相同的符号,而如果您有两个名称(字符串),则需要逐个元素地进行比较。


我认为这是Scheme所能达到的(至少我认为是R5RS和R6RS所能达到的)。但是,此符号概念有两种概括很重要。

未嵌入的符号。在上面,我将符号描述为被嵌入在某种表中的表,该表的键是命名符号的字符串。但是还有另一种可能性:您可以创建一个符号,然后在实际将其插入表中之前停止。结果是具有名称但该名称不可说的符号。未隔离的符号会破坏名称和符号之间的对等关系。特别是对于 interinter 符号,通常情况是,如果(string=? (symbol->string x) (symbol->string y))然后(eq? x y),但是如果一个或两个{ {1}}或x没有被实习。

这听起来像是一件无用的事情,但事实并非如此。

程序可能想要创建一个绝对可以确定不存在的符号。如果只有intern符号,则必须每次创建一个新名称,然后检查不存在具有该名称的符号。我上面描述的两个过程甚至都做不到,因为一旦您调用y,符号就存在:您d需要一个附加的string->symbol谓词,该谓词检查是否存在由其参数命名的符号,但如果不存在则不创建(然后在多线程实现中,您需要对所有事物都具有原子性,这会使事情变得更加复杂。 )。

一种解决方案就是创建一个未中断的符号:这是一个绝对可以确定的符号,因为它的名字是不确定的。

但是,为什么为什么程序会想要一个绝对确定不存在的符号?传统的答案是 macros :编写程序的程序。假设您正在编写某个宏,该宏有时会想要创建如下代码:

string-names-symbol?

好,这里有一个问题:如果您不控制的代码包含使用名为(let ((my-variable ...)) ... do something with my-variable ... ... some code I don't control is in here ... ... do something else with my-variable ...) 的变量怎么办:哎呀。但是,如果我安排生活,使我绑定的变量是一个名称不明的全新符号,那么我知道,我无法控制的代码不会谈论它,仅仅是因为它可以从来没有说出它的名字来获得它。好吧,这就是未中断符号的作用。

以下是使用此技巧的示例CL宏:

my-variable

这将使用CL (defmacro p1 (form &body forms) (let ((stashn (make-symbol "STASH"))) `(let ((,stashn ,form)) ,@forms ,stashn))) 函数创建一个新的未隔离符号,然后使用它捕获第一个子表单的值,然后评估剩余的子表单,然后返回保存的值。

这样的表格

make-symbol

展开至此

(p1 1
  2
  3)

其中(let ((#:stash 1)) 2 3 #:stash) 是CL用于未间断符号的符号。请注意,CL可以使用这种表示法在阅读器中创建未中断的符号,但是

#:

但是,上面的宏扩展中的两个符号实际上是相同的符号,您可以说服打印机向您显示它们:

> (eq '#:foo '#:foo)
nil

> (let ((*print-circle* t)) (pprint (macroexpand '(p1 1 2 3)))) (let ((#1=#:stash 1)) 2 3 #1#) #1=表示两件事是同一件事。是的,您也可以在阅读时使用它:

#1#

方案已采用另一种方法来解决此问题(这可能是R5RS和R6RS不提供处理未interinter符号的工具的原因),但是这种方法是您在Common Lisp等Lisps中处理它的方式,实例。

(请注意,未中断的符号使我认为一个符号不需要知道其名称的谎言:未中断的符号确实可以做到(尽管我可以想象一个未中断的符号根本没有名称的实现,所以,我不知道)。

更丰富的符号。一旦有了符号,就可以将各种东西挂起来,这是传统的做法。例如,Common Lisp中的符号在概念上是非常丰富的对象,具有:

  • 通过> (eq '#1=#:foo '#1#) t 的变量值(这与它们在代码中命名词法绑定变量的用法不同)
  • 通过symbol-value的函数值;
  • 通过symbol-function的商品清单;
  • 并且还用于命名类之类的东西。

CL还将使符号在其中被插入的一流对象(包)的表也变得如此,因此您可以对存在哪些符号进行自省,等等。

思考这样的事情的一种方法是,符号变成大对象:符号实际上具有容纳属于它们的各种事物的位置。但这不是必须的:这是一些Racket代码,它为符号提供了额外的symbol-plist插槽:

funge

因此,即使在CL中,符号中的所有其他“槽”实际上也不需要存在于符号本身中。


注意:想要谈论语言并带有符号来帮助他们谈论语言的语言,可能会或可能不会使用与谈论其他正在构建的语言相同的机制来谈论自身 使用语言。传统上,Lips在其自己的实现中显式地使用符号,但是现在情况比以前要少了。

我认为,

除了作为以该语言实现的对象之外,该方案根本不需要在其自身的描述中使用符号:该规范讨论“标识符”(请参阅​​Chris Vine对本文的评论),以及宏系统不依赖符号。

CL更为传统,符号在语言描述中起着更大的作用。特别是CL的宏系统明确地涉及操纵程序的源,这些程序的源表示为涉及具有名称的符号的结构:这就是为什么CL需要使用未中断的符号来创建唯一名称的原因。

但是,即使在CL情况下,符号也可能在实现中淡入背景。考虑以下CL函数定义:

#lang racket

(define funges (make-hasheq))

(define (symbol-funge? s)
  (hash-has-key? funges s))

(define (symbol-funge s (default (thunk
                                  (error "no funge"))))
  (hash-ref funges s default))

(define (set-symbol-funge! s v)
  (hash-set! funges s v))

(define (remove-symbol-funge! s)
  (hash-remove! funges s))

如果我在新的CL REPL(在仅使用(defun sum-tree (tree) (labels ((summit (branch sum) (etypecase branch (null sum) (number (+ sum branch)) (cons (summit (car branch) (summit (cdr branch) sum)))))) (summit tree 0))) 软件包的软件包中)以交互方式键入此内容,则会创建五个新符号:CLsum-tree,{{ 1}},treesummit,以及其中一个符号branch将命名一个函数,因此sum将为true,而sum-tree将返回一个功能。

如果我再用(fboundp sum-tree)编译此定义,这些符号将仍然存在。

但是如果我将此定义放在文件中并且(每次都在新的CL中):

  • 使用(fdefinition 'sum-tree)编译并加载文件;
  • 或使用(compile 'sum-tree)编译文件,启动新的CL ,然后使用(compile-file ... :load t)加载文件。

然后创建什么符号?在每种情况下,(compile-file ...)都被创建并绑定到一个函数,但是还不清楚是否创建了其他符号。在(load ...)时代,我敢肯定人们对sum-tree需要做什么产生了激烈的争论:使用comp.lang.lisp必需吗(是的,我认为是是),是否可以消除compile-file的副作用?可以取消read所插入符号的中断吗?在最终的情况下,如果将编译文件加载到一个完全新鲜的实例中,我认为很明显,唯一需要创建的符号是read

与CL类似,与某些非常旧的实现不同,

read

我可以期望在运行时对符号sum-tree进行自省,以找到其绑定的值(在宏扩展时可能会找出哪些符号)将在运行时绑定,尽管在CL中不是可移植的。

答案 1 :(得分:0)

在Common Lisp中,符号是由以下内容组成的值:

  • 一个名称,它是一个字符串
  • 可选软件包
  • (其他属性,此处不相关)

如果该符号没有包装,则称其为 uninterned 。 例如,这将产生一个未中断的符号:

CL-USER> (make-symbol "SOME-NAME")
#:SOME-NAME

如果两个符号具有相同的名称和相同的 non-nil 软件包,则它们是相同的。两个未交叉的符号永远不相等:

CL-USER> (eq (make-symbol "A") (make-symbol "A"))
NIL

当您在包中INTERN使用符号时,该符号将在该包中注册,并且进一步调用具有相同名称和名称的intern时,您会得到相同符号:

CL-USER> (eq (intern "A" "CL-USER") (intern "A" "CL-USER"))
T

基本上,符号是一种缓存标识符的方法。

通常,您不直接调用intern,这是Lisp阅读器自动完成的。这意味着一种形式是 read ,所有符号都解析为适当的基础符号,并且在运行时不进行名称查找(您不比较名称(字符串),而是按符号比较符号)身份)。