从Expanded form of fold in Racket中定义foldl,第一个函数的优势是否有任何优势:
(define (oldfoldl f init l)
(let loop ([init init] [l l])
(if (null? l) init
(loop (f (car l) init) (cdr l)))))
(define (myfoldl f init l)
(if (null? l) init
(myfoldl f (f (car l) init) (cdr l)) ))
换句话说,使用内部子功能有什么好处吗? 两者都给出相同的结果:
(oldfoldl + 0 '(1 2 3 4))
(myfoldl + 0 '(1 2 3 4))
输出:
10
10
编辑:使用define而不是let的第三个版本。它会有所不同:
(define (myfoldl2 f init l)
(define (loop [init init] [l l])
(if (null? l) init
(loop (f (car l) init) (cdr l)) ))
(loop init l) )
答案 0 :(得分:3)
对于您描述的简化定义,这两种形式实际上是完全等效的。唯一的区别是使用直接递归的那个在每次迭代时都会传递f
,init
和l
,而使用名为let
的传递仅传递init
}和l
。但是,我认为这种差异完全可以忽略不计。
但是,该简化版本不是答案顶部提到的实际foldl
函数,如下所示:
(define foldl
(case-lambda
[(f init l)
(check-fold 'foldl f init l null)
(let loop ([init init] [l l])
(if (null? l) init (loop (f (car l) init) (cdr l))))]
[(f init l . ls)
(check-fold 'foldl f init l ls)
(let loop ([init init] [ls (cons l ls)])
(if (pair? (car ls)) ; `check-fold' ensures all lists have equal length
(loop (apply f (mapadd car ls init)) (map cdr ls))
init))]))
这使用case-lambda
,它在参数计数上执行调度,以及一个名为check-fold
的内部函数,它执行参数类型检查。使用命名的let
而不是以递归方式调用foldl
可以避免在每次迭代时执行调度和运行时检查,这会产生更大的开销。
答案 1 :(得分:2)
是。通过保持不会因连续递归而变化的变量,您的眼睛会依赖于您正在查看的特定线上的重要值。
使用命名的let
只是定义过程然后使用它的好方法,但是变量和值在顶部。这使得更容易看到它是如何初始化的。
在你的例子中,所有这些对我来说都是好的,甚至是在连续递归中传递非变化变量时的第一个,但对于更复杂的过程,删除不变的绑定并创建帮助程序有助于使其更容易理解。
闭包/ lambda应该相当便宜,因此在let /过程中包装表达式在大多数情况下不应该对速度产生太大影响。
语言内部通常看起来更加神秘,所以我很惊讶这些实现非常简单。我打赌真正的工作是在球拍优化器中,这也有利于我们的用户代码。这三个版本在字节代码中可能无法区分。