如果我错了,请纠正我,但是在Java,C,C ++,Python,Javascript或我使用的任何其他语言中都没有像gensym那样的东西,而且我从来没有似乎需要它。为什么Lisp中有必要而不是其他语言?为了澄清,我正在学习Common Lisp。
答案 0 :(得分:9)
Common Lisp有一个强大的宏系统。您可以创建与您希望它们的行为完全相同的新语法。它甚至用它自己的语言表达,使语言中的所有内容都可以将代码从您想要编写的内容转换为CL实际理解的内容。具有强大宏系统的所有语言都提供gensym
或在其宏实现中隐式执行。
在Common Lisp中,如果要使代码与符号不匹配的代码使用结果中的任何其他位置,则使用gensym
。没有它,就不能保证用户使用宏实现者也使用的符号,并且他们开始干涉,结果与预期的行为不同。它确保同一宏的嵌套扩展不会干扰以前的扩展。使用Common Lisp宏系统,可以制作类似于Scheme syntax-rules
和syntax-case
的更严格的宏系统。
在Scheme中有几个宏系统。一个模式匹配,其中新引入的符号自动起作用,就好像它们是gensym
一样。 syntax-case
默认情况下也会使用gensym
创建新符号,并且还有一种减少卫生的方法。您可以使用defmacro
生成CL syntax-case
,但由于Scheme没有gensym
,您将无法使用它生成卫生宏。
Java,C,C ++,Python,Javascript都是Algol dialects,除了简单的基于模板的宏之外,它们都没有。因此他们没有gensym
,因为他们不需要它。由于在这些语言中引入新语法的唯一方法是希望它的下一个版本能够提供它。
有两种具有强大宏的Algol方言可供想到。 Nemerle和Perl6。它们都具有卫生方法,这意味着引入的变量就像使用gensym
一样。
在CL,Scheme,Nemerle,Perl6中,您无需等待语言功能。你可以自己制作!如果它们尚未可用,那么Java和PHP中的新闻很容易用其中的任何一个宏来实现。
答案 1 :(得分:8)
无法说出哪些语言具有等效GENSYM
。许多语言没有一流的符号数据类型(带有实体符号和未实体符号),许多语言没有提供类似的代码生成(宏,...)设施。
实习符号已在包中注册。 uninterned 不是。如果 reader (读者是Lisp子系统,它将文本s表达式作为输入并返回数据)在同一个包中看到两个内部符号并且具有相同的名称,则它假定它是相同的符号:
CL-USER 35 > (eq 'cl:list 'cl:list)
T
如果读者看到一个未加工的符号,它会创建一个新符号:
CL-USER 36 > (eq '#:list '#:list)
NIL
Uninterned 符号在名称前写有#:
。
GENSYM
来创建编号的未分隔符号,因为它有时在代码生成中有用,然后调试此代码。请注意,符号始终是新的,而不是eq
。但符号名称可能与另一个符号的名称相同。该数字为人类读者提供了关于身份的线索。
使用MAKE-SYMBOL
make-symbol
使用字符串参数作为名称创建一个新的 uninterned 符号。
让我们看一下这个函数生成一些代码:
CL-USER 31 > (defun make-tagbody (exp test)
(let ((start-symbol (make-symbol "start"))
(exit-symbol (make-symbol "exit")))
`(tagbody ,start-symbol
,exp
(if ,test
(go ,start-symbol)
(go ,exit-symbol))
,exit-symbol)))
MAKE-TAGBODY
CL-USER 32 > (pprint (make-tagbody '(incf i) '(< i 10)))
(TAGBODY
#:|start| (INCF I)
(IF (< I 10) (GO #:|start|) (GO #:|exit|))
#:|exit|)
以上生成的代码使用未分隔的符号。两个#:|start|
实际上都是相同的符号。如果我们有*print-circle*
到T
,我们会看到这一点,因为打印机会清楚地标记相同的对象。但是在这里我们没有得到这些附加信息。现在,如果您嵌套此代码,那么您会看到多个start
和一个exit
符号,每个符号都在两个地方使用。
使用GENSYM
现在让我们使用gensym
。 Gensym还创造了一个不间断的符号。可选地,该符号由字符串命名。添加了一个数字(参见变量CL:*GENSYM-COUNTER*
)。
CL-USER 33 > (defun make-tagbody (exp test)
(let ((start-symbol (gensym "start"))
(exit-symbol (gensym "exit")))
`(tagbody ,start-symbol
,exp
(if ,test
(go ,start-symbol)
(go ,exit-symbol))
,exit-symbol)))
MAKE-TAGBODY
CL-USER 34 > (pprint (make-tagbody '(incf i) '(< i 10)))
(TAGBODY
#:|start213051| (INCF I)
(IF (< I 10) (GO #:|start213051|) (GO #:|exit213052|))
#:|exit213052|)
现在,该数字表示两个未处理的#:|start213051|
符号实际上是相同的。当代码嵌套时,新版本的起始符号将具有不同的数字:
CL-USER 7 > (pprint (make-tagbody `(progn
(incf i)
(setf j 0)
,(make-tagbody '(incf ij) '(< j 10)))
'(< i 10)))
(TAGBODY
#:|start2756| (PROGN
(INCF I)
(SETF J 0)
(TAGBODY
#:|start2754| (INCF IJ)
(IF (< J 10)
(GO #:|start2754|)
(GO #:|exit2755|))
#:|exit2755|))
(IF (< I 10) (GO #:|start2756|) (GO #:|exit2757|))
#:|exit2757|)
因此,它有助于理解生成的代码,而无需打开*print-circle*
,这将标记相同的对象:
CL-USER 8 > (let ((*print-circle* t))
(pprint (make-tagbody `(progn
(incf i)
(setf j 0)
,(make-tagbody '(incf ij) '(< j 10)))
'(< i 10))))
(TAGBODY
#3=#:|start1303| (PROGN
(INCF I)
(SETF J 0)
(TAGBODY
#1=#:|start1301| (INCF IJ)
(IF (< J 10) (GO #1#) (GO #2=#:|exit1302|))
#2#))
(IF (< I 10) (GO #3#) (GO #4=#:|exit1304|))
#4#)
上面的内容对于Lisp 阅读器(读取文本表示的s表达式的子系统)是可读的,但对于人类读者而言则略少。
答案 2 :(得分:1)
我相信symbols(在Lisp意义上)在homoiconic语言中非常有用(那些语言的语法可以表示为该语言的数据)。
Java,C,C ++,Python,Javascript不是同性恋。
一旦有符号,就需要某种方式来动态创建符号。 gensym
是可能的,但你也可以intern。
BTW,MELT是类似lisp的方言,它不会使用gensym
创建符号,也不会使用clone_symbol创建实际字符串。 (实际上,MELT符号是预定义CLASS_SYMBOL
的实例,...)。
答案 3 :(得分:0)
gensym
可用作谓词。你可以在eponym库中找到它。