我已经使用lisp-family语言多年了,感觉我对它们有很好的把握.我现在正在编写自己的lisp(当然是时尚),但几乎完全避免重新实现Scheme,Common Lisp和朋友使用的相同模式.我总是发现奇怪的是所有的变种一个特别的事情let(letrec,flet,labels,let*...).
在一个"没有遗留下来的遗产"中表示,我希望能够写出如下内容:
(let ((x 7)
(y 8)
(z (* x y)))
(str "x * y * z = " (* x y z)))
Run Code Online (Sandbox Code Playgroud)
同样我希望能够写:
(let ((odd? (lambda (n) (if (zero? n) false (even? (dec n)))))
(even? (lambda (n) (if (zero? n) true (odd? (dec n)))))
(odd? 7))
Run Code Online (Sandbox Code Playgroud)
我可以通过在lambda体内使用define来非常有效地实现这两种变体.请原谅clojure-scheme-hybrid代码:
(defmacro let (bindings & body)
`((lambda ()
,@(map (lambda (name-value) (cons 'define name-value)) bindings)
,@body)))
Run Code Online (Sandbox Code Playgroud)
因为这只是在lambda体内的封闭环境中引入绑定,所以相互递归可以起作用,变量可以依赖于先前定义的存在.类似地,因为lambda关闭了创建它的环境,所以(define x expression) (define y expression)按预期扩展工作.
那么为什么有许多明确的形式会使问题复杂化?我忽略了什么吗?我已经实现了我自己let,如上所示,它似乎在所有情况下完全按预期工作.这也降低了嵌套函数应用程序的成本开销,例如在这种情况下let*.
内部定义由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)))
Run Code Online (Sandbox Code Playgroud)
在这里,在(+ 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)))
Run Code Online (Sandbox Code Playgroud)
这里,内部x的评估与let案例相同,但内部y的定义将使用内部x而不是外部x(但它仍使用外部y,因为内部y尚未绑定).因此内部x将是3,内部y将是1.
使用letrec和时letrec*,所有外部绑定都将被同名的内部绑定遮蔽,并且您无法访问外部x或y在这些情况下.该属性允许它们用于自引用函数和/或数据结构.
letrec并且letrec*是相似的,除了for之外,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"))))))
Run Code Online (Sandbox Code Playgroud)
测试结果如下:
> (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"
Run Code Online (Sandbox Code Playgroud)
希望这会使let类型之间的差异非常明显.:-)