SICP 递归 let 定义

问题描述

为了

(let ((fact 
        (lambda (n)
            (if (= n 1)
                1
                (* n (fact (- n 1)))))))
(fact 10))

方案出错,我想确认我的推理是否正确,

因为 (let ((var1 arg1)) body) 相当于在 var1 绑定到 arg1 的环境中评估正文。

上面,当我们在绑定调用之前尝试计算 arg1 时,我们没有找到引用,因为这正是我们试图绑定以使其可用

但是为什么这不抛出

(let ((fact (lambda (n) 
             (if (= n 1) 1 (* n (fact (- n 1))))))) 
  (display 10))

因为我们也试图在那里进行绑定。

那为什么不抛出呢

(define factorial
    (lambda (n)
      (if (= n 0)
          1
          (* n (factorial (- n 1))))))
          
(factorial 10)

解决方法

原因是“变量事实未绑定”错误仅在我们实际尝试查找“事实”的值时发生。考虑

(let ((fact (lambda (n) (if (= n 0)
                            1
                            (* n (fact (- n 1)))))))
 (fact 0))

这不会导致错误,因为我们没有输入 else 子句,因此不会尝试使用未绑定变量 fact 的值。但是,对 1 尝试相同的操作确实会给我们带来错误。

我们在使用 define 时不会遇到错误,因为 definelet 有不同的规则。在 let 绑定中,您创建了一个新的词法作用域,fact 绑定中的 let 绑定不会从 let“泄漏”到外部世界.由于 let 赋值的右侧只能引用来自外部世界的绑定,因此右侧不能引用绑定 fact

然而,在 define 语句中,重点是在当前作用域中引入一个新的绑定(而不是创建一个新的作用域)。在 define 绑定中,赋值的右侧必须仅引用来自外部世界的变量 - 但这可能包括 defined 的变量,只要访问它隐藏在 {{ 1}} 并因此懒惰地完成。

事实上,考虑以下几点:

lambda

在这种情况下,当我们实际调用 (define is-even (lambda (n) (or (= n 0) (is-odd (- n 1))))) (define is-odd (lambda (n) (and (not (= n 0)) (is-even (- n 1))))) (display (is-odd 55)) 时,is-evenis-odd 处于相同的范围内define,因此我们可以查找它的值。类似地,当我们调用 is-even 时,is-odd 已经定义,因此我们对其值进行了绑定。

请注意,以下内容不起作用

is-even

这是因为此处的 (define is-even (lambda (n) (or (= n 0) (is-odd (- n 1))))) (display (let ((is-odd (lambda (n) (and (not (= n 0)) (is-even (- n 1)))))) (is-odd 55))) 绑定is-odd 处于同一范围内。它是在比 is-even 更本地化的绑定中定义的。 is-even 定义中引用的 is-odd 不能与 is-even 语句中创建的 is-odd 相同,因为此 let 是本地绑定,因此不能在定义 is-odd 的更广范围内引用。

如果我们使用“动态范围”的老式 LISP 概念,也称为“按名称调用语义”,则后一个示例将工作得很好。但是,Scheme 不支持动态范围(在我看来这绝对是最好的)。