如果我错了,请纠正我,但是在Java,C,C++,Python,Javascript或我使用的任何其他语言中都没有像gensym那样的东西,我似乎从来没有需要它.为什么Lisp中有必要而不是其他语言?为了澄清,我正在学习Common Lisp.
Syl*_*ter 11
Common Lisp有一个强大的宏系统.您可以创建与您希望它们的行为完全相同的新语法.它甚至用它自己的语言表达,使语言中的所有内容都可以将代码从您想要编写的内容转换为CL实际理解的内容.具有强大宏系统的所有语言gensym
都在其宏实现中隐式提供或执行.
在Common Lisp中,gensym
当您想要使代码与符号不应与结果中任何其他位置使用的元素匹配时,您可以使用它.没有它,就不能保证用户使用宏实现者也使用的符号,并且他们开始干涉,结果与预期的行为不同.它确保同一宏的嵌套扩展不会干扰以前的扩展.使用Common Lisp宏系统,可以制作类似于Scheme syntax-rules
和更类似的更严格的宏系统syntax-case
.
在Scheme中有几个宏系统.一个模式匹配,其中新引入的符号自动起作用,就好像它们一样gensym
.syntax-case
默认情况下也会制作新的符号,就好像它们一样,gensym
并且还有一种减少卫生的方法.您可以使用CL defmacro
,syntax-case
但由于Scheme没有,gensym
您将无法使用它制作卫生的宏.
Java,C,C++,Python,Javascript都是Algol方言,除了简单的基于模板的宏外,它们都没有.因此他们没有,gensym
因为他们不需要它.由于在这些语言中引入新语法的唯一方法是希望它的下一个版本能够提供它.
有两种具有强大宏的Algol方言可供想到.Nemerle和Perl6.它们都具有卫生方法,这意味着引入的变量就像它们的制作一样gensym
.
在CL,Scheme,Nemerle,Perl6中,您无需等待语言功能.你可以自己制作!Java和PHP中的新闻很容易用其中任何一个中的宏实现,如果它还没有可用的话.
不能说哪种语言有相当的GENSYM
.许多语言没有第一类符号数据类型(带有实体符号和未实体符号),许多语言没有提供类似的代码生成(宏,...)工具.
一个实习标志登记在包.一个没有取消的不是.如果读者(读者是将文本s表达式作为输入并返回数据的Lisp子系统)在同一个包中看到两个内部符号并且具有相同的名称,则它假定它是相同的符号:
CL-USER 35 > (eq 'cl:list 'cl:list)
T
Run Code Online (Sandbox Code Playgroud)
如果读者看到一个未加工的符号,它会创建一个新符号:
CL-USER 36 > (eq '#:list '#:list)
NIL
Run Code Online (Sandbox Code Playgroud)
未加密的符号#:
在名称前面写入.
GENSYM
在Lisp中用于创建编号的未分隔符号,因为它有时在代码生成中有用,然后调试此代码.请注意,符号始终是新的,而不是eq
其他任何符号.但符号名称可能与另一个符号的名称相同.该数字为人类读者提供了关于身份的线索.
一个使用的例子 MAKE-SYMBOL
make-symbol
使用字符串参数作为名称创建新的未分隔符号.
让我们看看这个函数生成一些代码:
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|)
Run Code Online (Sandbox Code Playgroud)
上面生成的代码使用未分隔的符号.两者#:|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|)
Run Code Online (Sandbox Code Playgroud)
现在,该数字表示两个未分隔的#:|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|)
Run Code Online (Sandbox Code Playgroud)
因此,它有助于理解生成的代码,而无需打开*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#)
Run Code Online (Sandbox Code Playgroud)
上面的内容对于Lisp 阅读器(读取用于文本表示的s表达式的子系统)是可读的,但对于人类阅读器来说略微少一些.