为什么不用词法封闭的`define`来实现`let`?

d11*_*wtq 4 lisp scheme

我已经使用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*.

Chr*_*ung 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)))
Run Code Online (Sandbox Code Playgroud)

在这里,在(+ x y)(- x y)表达,我们使用的是外部的绑定xy.因此内部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*,所有外部绑定都将被同名的内部绑定遮蔽,并且您无法访问外部xy在这些情况下.该属性允许它们用于自引用函数和/或数据结构.

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类型之间的差异非常明显.:-)