let*在Chez Scheme/Racket中是如何定义的?



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*宏中,自由标识符是letlet*,因为它们没有在宏中的其他地方绑定(像bindingnextbody那样)。

这意味着在该宏中,letlet*将具有宏定义时的值,并且用户代码(围绕宏的使用)将不会对使用的letlet*的值产生影响。

实现此卫生的一种方法是通过重命名。因此,通过重命名,上面的宏可以重命名如下:
(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 ...)))))

这里,g1g6是生成的临时符号,通常被称为"gensyms"(在Lisp函数gensym之后,它创建了这样的东西)。请注意,由于重命名,用户代码不能影响宏中letlet*的定义,并且宏对bindingnextbody的绑定也不会影响任何可能在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绑定混淆。

相关内容

  • 没有找到相关文章

最新更新