为什么 lisp 使用 gensym 而其他语言不使用?

Why does lisp use gensym and other languages don't?

如果我错了请纠正我,但在 Java、C、C++、Python、Javascript 或任何其他语言中没有像 gensym 这样的语言已经用过了,而且我似乎从来不需要它。为什么它在 Lisp 中是必要的而不是在其他语言中?为了澄清,我正在学习 Common Lisp。

不能说哪些语言具有 GENSYM 的等价物。许多语言没有 first-class 符号数据类型(带有 interned 和 uninterned 符号)并且许多语言不提供类似的代码生成(宏,...)工具。

一个 interned symbolpackage 中注册。 uninterned 不是。如果 reader(reader 是 Lisp 子系统,它将文本 s 表达式作为输入和 returns 数据)在相同的包和相同的名称,它假定它是相同的符号:

CL-USER 35 > (eq 'cl:list 'cl:list)
T

如果 reader 看到一个未嵌入的符号,它会创建一个新符号:

CL-USER 36 > (eq '#:list '#:list)
NIL

Uninterned符号在名字前写#:

GENSYM 在 Lisp 中用于创建 numbered uninterned 符号,因为它有时在代码生成和调试此代码时很有用。请注意,这些符号始终是新的,而不是 eq 其他任何东西。但是符号名称可以与另一个符号的名称相同。该数字为人类 reader 提供了有关身份的线索。

使用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 还创建了一个 uninterned 符号。可选地,此符号由字符串命名。添加了一个数字(参见变量 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 reader(读取文本表示的 s 表达式的子系统)来说是可读的,但对于人类来说就少了一点reader.

我相信 symbols (in the Lisp sense) are mostly useful in homoiconic 种语言(语言的语法可以表示为该语言的数据)。

Java、C、C++、Python、Java脚本不是谐音。

有了符号后,您需要一些方法来动态创建它们。 gensym 是一种可能性,但您也可以 intern 他们。

顺便说一句,MELT is a lisp-like dialect, it does not create symbols with gensym or by interning strings but with clone_symbol。 (实际上 MELT 符号是预定义 CLASS_SYMBOL, ... 的实例)。

Common Lisp 拥有强大的宏系统。您可以创建新的语法模式,使其完全按照您希望的方式运行。它甚至用自己的语言表达,使语言中的一切都可用,以将代码从您要编写的代码转换为 CL 实际理解的代码。所有具有强大宏系统的语言都提供 gensym 或在其宏实现中隐式提供。

在 Common Lisp 中,当您想要编写代码时,您可以使用 gensym,其中的符号不​​应与结果中任何其他位置使用的元素匹配。没有它,就无法保证用户使用宏实现者也使用的符号,并且他们开始干扰并且结果与预期行为不同。它确保同一宏的嵌套扩展不会干扰以前的扩展。使用 Common Lisp 宏系统,可以创建类似于 Scheme syntax-rules and syntax-case.

的更具限制性的宏系统

Scheme中有几个宏系统。一种模式匹配,其中新引入的符号会自动运行,就好像它们是用 gensym 制作的一样。 syntax-case 也会默认生成新符号,就好像它们是用 gensym 生成的一样,并且还有一种减少卫生的方法。您可以使用 syntax-case 制作 CL defmacro,但由于 Scheme 没有 gensym,您将无法使用它制作卫生宏。

Java、C、C++、Python、Java 脚本都是 Algol dialects 和 none 它们除了简单的基于模板的宏之外。因此他们没有 gensym 因为他们不需要它。由于在这些语言中引入新语法的唯一方法是希望它的下一个版本能够提供它。

我想到了两种具有强大宏的 Algol 方言。 Nemerle and Perl6。它们都采用卫生方法,这意味着引入的变量的行为就好像它们是用 gensym.

制作的一样

在 CL、Scheme、Nemerle、Perl6 中,您无需等待语言特性。你可以自己做! Java 和 PHP 中的新闻可以很容易地用其中任何一个中的宏实现,如果它还不可用的话。

gensym 在大多数 Prolog 解释器中可用作谓词。您可以在人名库中找到它。