Chez Scheme / Racket如何定义let*
?特别是,为什么第一个例子评估为6 ...
(let* ((let +) (a (let 2 4)))
a)
...当我对exercise 3.1.3的理解是let*
可以扩展为嵌套let
(甚至是嵌套的let*
)语句时,可以将上面的示例扩展为人们会期望解释器在错误中做结果吗?
(let ((let +))
(let (a (let 2 4))
a))
实施是否与练习不同?我希望第一个例子由于let
的新定义而导致错误。
答案 0 :(得分:5)
(let *([let +] [a(let 2 4)])a)
变为
(LET([let +]) (LET([a(let 2 4)]) a))的
其中LET指的是定义let *的地方的“宏”(正如Chris写的那样:“卫生”)。
当评估它时,LET会将+的值绑定到let。 计算(let 2 4)的值,这是6(由于let的绑定)。 然后6绑定到a。最后评估身体,由于a与6绑定,结果为6。
答案 1 :(得分:2)
让我们假设let*
的这个定义(我试图让这个变得尽可能简单,所以它不是"工业强度"作为Astau Takikawa所关联的球拍):
(define-syntax let*
(syntax-rules ()
;; base case
((_ () body ...)
(let ()
body ...))
;; recursive case
((_ (binding next ...) body ...)
(let (binding)
(let* (next ...)
body ...)))))
Scheme有一个名为 hygiene 的概念,它表示宏中的任何免费标识符(即宏中未定义的标识符)都将绑定到宏的值#39的定义。对于上述let*
宏,免费标识符为let
和let*
,因为它们未在其他位置绑定(binding
,{{1}在宏中,和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
是生成的临时符号,通常称为" gensyms" (在Lisp函数g6
之后,创建了这样的东西)。请注意,由于重命名,用户代码不会影响宏中gensym
和let
的定义,也不会影响let*
,{{1}的宏绑定}和binding
不会影响可能在next
正文中使用此类标识符的任何用户代码。
脚注(如果您的学生需要对此进行更深入的处理):对于许多Scheme实现,gensyms是未处理的(它们不会进入符号池,与普通符号不同,它们都是实体符号)。然后,即使用户碰巧正确地猜测"由重命名过程生成的标识符(例如,即使它们碰巧在上面的示例中使用body
,let*
等),它们实际上也不会与宏的标识符冲突实际使用。
然而,标准Scheme没有讨论未加密的符号,并且在标准Scheme的上下文中,所有符号都被实现,因此它对于Scheme实现来说完全有效,即使对于gensyms,也只能使用实际符号。在这种情况下,可以通过与重命名的符号碰撞来创建破坏卫生的方法。
答案 2 :(得分:0)
来自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
时let*
定义了let
时,Scheme卫生宏不会混淆let*
绑定。