let*
在Chez Scheme/Racket中是如何定义的?特别是,为什么第一个例子的值是6…
(let* ((let +) (a (let 2 4)))
a)
…当我从练习3.1.3中理解到let*
可以扩展为嵌套的let
(甚至嵌套的let*
)语句时,但是扩展上面的示例,就像人们期望解释器做的那样会导致错误?
(let ((let +))
(let (a (let 2 4))
a))
实现与练习中不同吗?我希望第一个例子也会由于let
的新定义而导致错误。
(让*([让+][(让2 4))))
是
(LET ([LET +]))(LET (a (LET) 4)))
,其中LET指的是定义LET *的地方的LET"宏"(如Chris写的:"卫生学")。
当this被求值时,LET将+的值绑定到LET。计算(let 2 4)的值,这是6(由于let的绑定)。然后将6绑定到a。最后对主体进行求值,由于a绑定到6,因此结果为6。
让我们假设let*
的定义(我试图使其尽可能简单,所以它不像Asumu Takikawa所链接的球拍那样具有"工业强度"):
(define-syntax let*
(syntax-rules ()
;; base case
((_ () body ...)
(let ()
body ...))
;; recursive case
((_ (binding next ...) body ...)
(let (binding)
(let* (next ...)
body ...)))))
Scheme有一个名为卫生的概念,它表示宏中的任何自由标识符(即未在宏中定义的标识符)将被绑定到宏定义时的值。在上面的let*
宏中,自由标识符是let
和let*
,因为它们没有在宏中的其他地方绑定(像binding
、next
和body
那样)。
这意味着在该宏中,let
和let*
将具有宏定义时的值,并且用户代码(围绕宏的使用)将不会对使用的let
和let*
的值产生影响。
(define-syntax let*
;; bind g1 to current let, g2 to current let*
(syntax-rules ()
((_ () g3 ...)
(g1 ()
g3 ...))
((_ (g4 g5 ...) g6 ...)
(g1 (g4)
(g2 (g5 ...)
g6 ...)))))
这里,g1
到g6
是生成的临时符号,通常被称为"gensyms"(在Lisp函数gensym
之后,它创建了这样的东西)。请注意,由于重命名,用户代码不能影响宏中let
和let*
的定义,并且宏对binding
、next
和body
的绑定也不会影响任何可能在let*
主体中使用这些标识符的用户代码。
脚注(以防你的学生想要对此进行更深入的处理):对于许多Scheme实现,gensyms是未被存储的(它们不像普通符号那样被存储到符号池中)。然后,即使用户碰巧正确地"猜测"了重命名过程生成的标识符(例如,即使他们在上面的例子中碰巧使用了g1
, g2
等),它们也不会与宏实际使用的标识符发生冲突。
然而,标准Scheme不讨论非内部的符号,并且在标准Scheme的上下文中,所有的符号都是内部的,因此Scheme实现只使用内部的符号是完全有效的,甚至对于gensyms也是如此。在这种情况下,可以通过与重命名的符号冲突来创建破坏卫生的方法。
来自R7RS的let*
的官方定义:
(define-syntax let*
(syntax-rules ()
((let* () body1 body2 ...)
(begin body1 body2 ...))
((let* ((name1 val1) (name2 val2) ...) body1 body2 ...)
(let ((name1 val1))
(let* ((name2 val2) ...)
body1 body2 ...)))))
显示let*
展开为嵌套的let
表达式。出现错误的原因是,当定义let*
时,Scheme卫生宏不会将let
绑定与使用let*
时的let
绑定混淆。