为什么我们已经有 let 时还需要 letrec?

Why is letrec necessary when we already have let?

在R7RS-small section 4.2.2 Binding constructs中,有一个例子letrec:

(letrec ((even?
          (lambda (n)
            (if (zero? n)
                #t
                (odd? (- n 1)))))
         (odd?
          (lambda (n)
            (if (zero? n)
                #f
                (even? (- n 1))))))
  (even? 88))

我不明白为什么 letrec 是必要的,因为我们的语言已经有了 let。我尝试用 let 替换 letrec,并且在 MIT Scheme 11.2、Racket 7.2 plt-r5rs、Chez Scheme 9.5 和 Guile 3.0 中得到了正确的值 (#t)。 1. letrec的真正用途是什么?

(let ((even?
       (lambda (n)
         (if (zero? n)
             #t
             (odd? (- n 1)))))
      (odd?
       (lambda (n)
         (if (zero? n)
             #f
             (even? (- n 1))))))
  (even? 88))

letrec 不能这样换成let。它 'works' 在你的情况下,因为你的本地绑定正在隐藏全局绑定,然后在看起来递归但不是递归的调用中调用它们。考虑一下

(let ((factorial
       (λ (n)
         (if (<= n 1)
        1
        (* n (factorial (- n 1)))))))
  (factorial 10))

如果您尝试对此进行评估,您将收到错误消息。但是,如果您使用 letrec,则不会:

(letrec ((factorial
          (λ (n)
            (if (<= n 1)
                1
                (* n (factorial (- n 1)))))))
  (factorial 10))

很好。

当然,如果您愿意通过将函数本身作为参数传递来使用与 U 组合器非常相似的东西,那么您实际上可以不用 letrec 就可以离开:

(let ((factorial
       (λ (c n)
         (if (<= n 1)
        1
        (* n (c c (- n 1)))))))
  (factorial factorial 10))

但是,好吧,你也不需要let:几乎所有的东西都只是一种语法上的便利。