在函数Racket中使用内循环的优点

时间:2016-08-18 16:57:23

标签: scheme racket

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

2 个答案:

答案 0 :(得分:3)

对于您描述的简化定义,这两种形式实际上是完全等效的。唯一的区别是使用直接递归的那个在每次迭代时都会传递finitl,而使用名为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 /过程中包装表达式在大多数情况下不应该对速度产生太大影响。

语言内部通常看起来更加神秘,所以我很惊讶这些实现非常简单。我打赌真正的工作是在球拍优化器中,这也有利于我们的用户代码。这三个版本在字节代码中可能无法区分。

相关问题