我一直在使用lisp-family语言多年,感觉我对它们有很好的把握。我现在正在编写自己的lisp(当然是时尚),但几乎完全避免重新实现Scheme,Common Lisp和朋友使用的相同模式。我总觉得奇怪的一件事是let
(letrec
,flet
,labels
,let*
......)的所有变体。
说出过去没有带来的遗产"实施lisp,我希望能够写出如下内容:
(let ((x 7)
(y 8)
(z (* x y)))
(str "x * y * z = " (* x y z)))
同样我也希望能够写下来:
(let ((odd? (lambda (n) (if (zero? n) false (even? (dec n)))))
(even? (lambda (n) (if (zero? n) true (odd? (dec n)))))
(odd? 7))
我可以通过在lambda体内使用define来非常有效地实现这两种变体。请原谅clojure-scheme-hybrid代码:
(defmacro let (bindings & body)
`((lambda ()
,@(map (lambda (name-value) (cons 'define name-value)) bindings)
,@body)))
因为这只是在lambda体内的封闭环境中引入了绑定,所以相互递归可以起作用,变量可以依赖于先前定义的存在。同样,因为lambda会关闭创建它的环境,所以扩展(define x expression) (define y expression)
会按预期工作。
那么为什么有许多明确的形式会使问题复杂化?我忽略了什么吗?我已经实现了我自己的let
,如上所示,它似乎在所有情况下都完全符合预期。这也降低了嵌套函数应用程序的成本开销,例如let*
。
答案 0 :(得分:8)
内部定义由R5RS定义(har har)以使用letrec
,R6RS和R7RS定义使用letrec*
。您所描述的行为恰恰是letrec*
。
但是,在某些情况下,您希望使用外部绑定,并且您不希望内部绑定在定义期间将它们遮挡。在这种情况下,let
和let*
比letrec
和letrec*
更合适。
这是什么意思?这是let
提供letrec
没有的外部范围的一种方式:
(let ((x 1)
(y 2))
(let ((x (+ x y))
(y (- x y)))
(format #t "x = ~a, y = ~a~%" x y)))
此处,在(+ x y)
和(- x y)
表达式中,我们使用x
和y
的外部绑定。因此,内部x
将为3,内部y
将为-1。
使用let*
类似,只是绑定是连续的:
(let ((x 1)
(y 2))
(let* ((x (+ x y))
(y (- x y)))
(format #t "x = ~a, y = ~a~%" x y)))
此处,内部x
的评估与let
案例相同,但内部y
的定义将使用内部x
而不是外部x
y
(但它仍使用外部y
,因为内部x
尚未绑定)。因此,内部y
将为3,内部letrec
将为1。
使用letrec*
和x
时,所有外部绑定都将被同名的内部绑定所遮蔽,并且您无权访问外部y
或{{ 1}}在那些情况下。此属性允许将它们用于自引用函数和/或数据结构。
letrec
和letrec*
是相似的,除了letrec
之外,所有值都先评估,然后同时绑定到末尾的变量;而对于letrec*
,每个变量的值都是从左到右依次计算和绑定的。
为了演示四种let
类型,我写了一个小的Racket宏,它允许你测试每个类型的行为:
#lang racket
(define-syntax test-let
(syntax-rules ()
((_ let)
(let ((x "outer x")
(y "outer y")
(p (lambda (x y label)
(printf "~a: x = ~s, y = ~s~%" label x y))))
(let ((before (p x y "before"))
(x (begin
(p x y "during x")
"inner x"))
(between (p x y "between"))
(y (begin
(p x y "during y")
"inner y"))
(after (p x y "after")))
(p x y "body"))))))
测试结果:
> (test-let let)
before: x = "outer x", y = "outer y"
during x: x = "outer x", y = "outer y"
between: x = "outer x", y = "outer y"
during y: x = "outer x", y = "outer y"
after: x = "outer x", y = "outer y"
body: x = "inner x", y = "inner y"
> (test-let let*)
before: x = "outer x", y = "outer y"
during x: x = "outer x", y = "outer y"
between: x = "inner x", y = "outer y"
during y: x = "inner x", y = "outer y"
after: x = "inner x", y = "inner y"
body: x = "inner x", y = "inner y"
> (require rnrs/base-6)
> (test-let letrec)
before: x = #<undefined>, y = #<undefined>
during x: x = #<undefined>, y = #<undefined>
between: x = #<undefined>, y = #<undefined>
during y: x = #<undefined>, y = #<undefined>
after: x = #<undefined>, y = #<undefined>
body: x = "inner x", y = "inner y"
> (require rnrs/base-6)
> (test-let letrec*)
before: x = #<undefined>, y = #<undefined>
during x: x = #<undefined>, y = #<undefined>
between: x = "inner x", y = #<undefined>
during y: x = "inner x", y = #<undefined>
after: x = "inner x", y = "inner y"
body: x = "inner x", y = "inner y"
希望这会使let
类型之间的差异非常明显。 : - )
答案 1 :(得分:2)
define
(或者,因为它是相同的)是一个letrec
,直到R5RS和来自R6RS的letrec*
和当前的R7RS。你可以使用letrec*
做任何事情,除了掩盖你自己的变量(它们变得未定义)。
(let ((x 10))
(letrec ((x x))
x)) ;; returns some undefined value
变为
(let ((x 10))
(let ((x 'undefined))
(let ((tmpx x)) ;; notice x is already shadowed by the new binding
(set! x tmpx)
x))) ;; returns some undefined value
原因是因为变量的评估需要在已定义变量的情况下完成,以便可以递归。您的相互递归odd?
取决于它。虽然两个以相同的方式将新x设置为旧x,因为它们从不同时存在。
您可能知道(let ((x 10) (y 20)) ...)
只是匿名过程调用((lambda (x y) ...) 10 20)
的语法糖,let*
是嵌套的(let* ((x 10) (y 20)) ...) => (let ((x 10)) (let ((y 20)) ... )))
。命名let
是letrec
,而letrec*
几乎嵌套letrec
(所有变量都是在未初始化的值中生成的,所以所有级别都可以引用它们)
如果你想绑定10个变量并且可以在一个闭包中完成它,那么使用define
执行它时最便宜的是引入20个过程调用和10个突变。这可能是现代翻译人员今天无法管理的,但是当它被发明时,Scheme并没有表现出那么好的表现,如果它只有letrec*