What are the merits of letrec as a programming language feature
我已经看过关于letrec的所有内容了,我仍然不明白它为一种语言带来了什么。 似乎所有用letrec表达的东西都可以很容易地写成递归函数。 但是,如果语言已经支持递归函数,是否有任何理由将letrec作为编程语言的一个特性公开? 为什么有几种语言同时暴露?
我得到letrec可能用于实现其他功能,包括递归函数,但这与它本身应该是一个功能的原因无关。 我还读到有些人发现它比某些lisps中的递归函数更具可读性,但同样这也不相关,因为该语言的设计者可以努力使递归函数足够可读,不需要其他功能。 最后,我被告知letrec可以更简洁地表达某些类型的递归值,但我还没有找到一个激励的例子。
TL; DR:
考虑
1 | let fact = fun (n => (n==0 -> 1 ; n * fact (n-1))) |
在这个定义的正文中,名称
内部
您引用的
如果没有定义实体的能力来引用自身,即在具有非递归
1 | let fact = (fun (f => f f)) (fun (r => n => (n==0 -> 1 ; n * r r (n-1)))) |
因此
然后问题成为,为什么要暴露非递归
你要求一个动机的例子。考虑将Fibonacci数定义为自引用惰性列表:
1 | letrec fibs = {0} + {1} + add fibs (tail fibs) |
对于非递归
并且假设
需要的是
注意:虽然这不是Scheme特定的问题,但我正在使用Scheme来证明这些差异。希望你能读一些小的lisp代码
1 2 3 4 5 6 | (define (fib n) (let ((fib (lambda (n a b) (if (zero? n) a (fib (- n 1) b (+ a b)))))) (fib n)) |
此代码失败,因为
1 2 3 4 5 6 | (define (fib n) (letrec ((fib (lambda (n a b) (if (zero? n) a (fib (- n 1) b (+ a b)))))) (fib n)) |
那个
1 2 3 4 5 6 7 8 | (define (fib n) (let ((fib 'undefined)) (let ((tmp (lambda (n a b) (if (zero? n) a (fib (- n 1) b (+ a b)))))) (set! fib tmp)) (fib n))) |
所以在这里你清楚地看到
那么当你创建一个全局函数时会发生什么?显然,如果要进行递归,则需要在评估lambda之前存在或者必须突变环境。它也需要解决同样的问题。
在函数式语言实现中,突变不正常,您可以使用Y(或Z)组合器解决此问题。
如果您对如何实现语言感兴趣,我建议您从Matt Mights文章开始。