【问题标题】:SICP recursive let definitionSICP递归let定义
【发布时间】:2021-08-24 02:36:33
【问题描述】:

对于

(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)

【问题讨论】:

    标签: scope scheme let sicp


    【解决方案1】:

    原因是“variable fact is not bound”错误只有在我们实际尝试查找“fact”的值时才会出现。考虑

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

    这不会导致错误,因为我们没有输入else 子句,因此不会尝试使用未绑定变量fact 的值。然而,用1 尝试同样的事情确实会给我们带来错误。

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

    然而,在define 语句中,重点是在当前作用域中引入一个新的绑定(而不是创建一个新作用域)。在define 绑定中,赋值的右侧必须只引用来自外部世界的变量——但这可能包括变量defined,只要访问它隐藏在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-even 时,is-odddefined,与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-even 定义中引用的is-odd 不能与在let 语句中创建的is-odd 相同,因为这个is-odd 是一个本地绑定,因此不能在@ 更广泛的范围内引用987654354@ 中定义。

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

    【讨论】:

    • 谢谢,只是一个后续 - 如果我们将 let 与 let a 3 之类的变量一起使用,与如果它与函数 let fact lambda(n) (..) 一起使用,那么绑定的时间会有所不同。是否仅在调用 fact 时才进行第二个函数中的绑定?如果没有,那么即使(let (fact lambda(b) (..)) (fact 0) 也应该抛出一个错误。 let 对函数调用进行绑定听起来很奇怪。 (让他们说的是它首先进行绑定,然后使用该绑定评估其主体)。非常感谢!
    • @user10206517 不,它不会抛出错误,因为该错误仅在我们尝试访问未绑定变量的值时发生,不是当我们定义一个函数时其中一个未绑定的变量。您的示例实际上甚至比我使用(fact 0) 的初始示例更不极端——我们可以很好地计算(fact 0),因为计算(fact 0) 不会碰到导致错误的if 语句的错误分支。在(display 8) 的情况下,我们甚至根本不调用fact,因此没有遇到未绑定变量的风险。
    • 函数调用后是否让被绑定?调用在 let's body 中,所以在调用 body 之前不应该先绑定 fact。例如。如果是 (let (a 2) (display (+ a 2)) ,则 a 将绑定到 2,然后执行主体。如果发生类似的事情,那么在引用时 fact 已经被绑定并因此被定义跨度>
    • 或者你是说它是绑定的,当它试图评估 lambda 时它找不到 fact 因为它只在外部环境中查找,而不是在它会找到它的当前环境中查找当然
    • @user10206517 是的。 fact 确实在我们执行 (let (fact (lambda ...)) (fact 0)) 示例时定义 - 否则,我们根本无法计算 (fact 0)。但它是在let 语句创建的新环境中定义的。值(lambda (n) (if (= n 0) ...)) 创建一个过程,该过程在评估时使用不包括factold 环境。另一方面,define 语句创建的过程使用当前环境,define 语句通过添加fact 的绑定来修改它。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-01-19
    • 1970-01-01
    • 2014-01-23
    • 1970-01-01
    • 2021-09-20
    • 2020-07-23
    • 1970-01-01
    相关资源
    最近更新 更多