配合Racket的左折叠功能,
(define (foldl cons z ls)
(if (null? ls)
z
(foldl cons (cons (car ls) z) ; NB! args order
(cdr ls))))
我们可以轻松实现列表的求和 ((foldl + 0 xs)), 或 取平方, 或 分别进行过滤。
嵌套它们也很容易,以便一个与另一个的结果一起使用(如其他答案所示),但这意味着 三个 执行单独的列表遍历,
(define (sqr x) (* x x))
(define (foo-3 xs)
(foldl + 0
(foldl (lambda (x acc) (cons (sqr x) acc)) '()
(foldl (lambda (x acc) (if (even? x) (cons x acc) acc)) '()
xs))))
但实际上,使用 reducer 函数(如 +)折叠(或“reducing”)列表会替换列表的cons 无论如何都要使用那个函数的应用程序,那么为什么不直接使用那个 reducer 呢?这意味着嵌套的折叠可以融合在一起为
(define (foo-2 xs)
(foldl (lambda (x acc) (+ (sqr x) acc)) 0
(foldl (lambda (x acc) (if (even? x) (cons x acc) acc)) '()
xs)))
还有,作为
(define (foo-1 xs) ; one traversal, tail-recursive, iterative!
(foldl (lambda (x acc) (if (even? x) (+ (sqr x) acc) acc)) 0
xs))
因此派生 迭代单遍历函数,否则可以相对容易地手动编码,但作为递归变体(见在其他答案中)。
我们看到这里有必要抽象cons,这样就可以很容易地操作、替换它。换句话说,我们想要
(lambda (x acc) (cons (sqr x) acc)) ==: ((mapping sqr) cons)
(lambda (x acc) (if (even? x) (cons x acc) acc)) ==: ((filtering even?) cons)
从而得到所谓的“转换器”,即reducer 函数的修饰符:
(define (((mapping f) kons) x acc) (kons (f x) acc)) ; "mapping" transducer
(define (((filtering p) kons) x acc) (if (p x) (kons x acc) acc)) ; "filtering" one
(define (foo xs)
(foldl + 0
(foldl ((mapping sqr) cons) '()
(foldl ((filtering even?) cons) '()
xs)))
=
(foldl ((mapping sqr) +) 0 ; replace the constructor!
(foldl ((filtering even?) cons) '() ; and the sentinel value
xs))
=
(foldl ((filtering even?) ((mapping sqr) +)) 0 ; and again!
; (lambda (x acc) (if (even? x) (+ (sqr x) acc) acc)) ; look, ma, no cons!
xs)
)
(f (g x)) 模式被抽象为功能组合,
(define ((compose1 f g) x)
(f (g x)))
所以(f (g x)) 是((compose1 f g) x)。使用更通用的compose 接受任何个要组合的函数,
(define ((compose . fs) x)
(if (null? fs)
x
((car fs) ((apply compose (cdr fs)) x))))
我们可以用更通用的方式对其进行编码,将我们可能需要的许多转换器组合到一个组合转换器中,为它输入的每个参数执行组合操作(取自输入序列;这里是一个列表):
(define (transduce-list tducr op z xs)
(foldl ; lists are reduced with foldl
(tducr op) ; the full reducer is created by applying
z xs)) ; the transducer to the final reducer
(define (foo xs) ; sum the squares of evens in a list
(transduce-list ; by transducing the list
(compose ; with the transducer composed from
(filtering even?) ; a filtering step and
(mapping sqr)) ; a mapping step
+ 0 xs)) ; with + as the final reducer
就是这样!
所以我们有
(define (sqr1 x) (+ 1 (* x x))) ; for clearer testing results
> (foldl ((mapping sqr1) cons) '() '(1 2 3 4))
'(17 10 5 2)
> (foldl ((mapping sqr1) +) 0 '(1 2 3 4))
> 34
((mapping sqr1) cons),就像cons 本身一样,是一个有两个参数的函数,因此可以用作foldl 的reducer 函数 参数。
(define g ((mapping sqr1) cons)) 与
相同
(define (g x acc)
(cons (sqr1 x) acc))
filtering 我们有
> (foldl ((filtering even?) +) 0 '(1 2 3 4))
> 6
> (foldl ((mapping sqr1) ((filtering even?) cons)) '() '(1 2 3 4))
> '(10 2)
> (foldl ((filtering even?) ((mapping sqr1) cons)) 0 '(1 2 3 4))
> '(17 5 . 0)
所以,((mapping sqr1) ((filtering even?) cons)) 是一个减速器,(mapping sqr1) 使用 ((filtering even?) cons) 作为 其 减速器。而 that 是 (filtering even?) 使用 cons 作为 its - 链中的最终 - 减速器功能:
(define g
((mapping sqr1) ((filtering even?) cons)))
=
(define (g x acc)
(let ((f ((filtering even?) cons)))
(f (sqr1 x) acc))) ; by definition of mapping
=
(define (g x acc)
(define (f y acc)
(if (even? y) (cons y acc) acc)) ; by definition of filtering
(f (sqr1 x) acc))
=
(define (g x acc)
(let ((y (sqr1 x)))
(if (even? y) (cons y acc) acc))) ; by application rule
嗯,映射、过滤和 consing 都自动为我们汇总到 一个 reducer 函数中,就好像我们自己编写的一样!更好的是,foldl 是尾递归的,整个函数是迭代的,并且只执行一次列表遍历——因为三个 reducer 函数合并为一个。
更多测试:
(define (bar xs)
(foldl ((compose
(filtering even?) ; filtering is done first
(mapping sqr1))
cons)
0 xs))
(define (baz xs)
(foldl ((compose
(mapping sqr1) ; mapping is done first
(filtering even?))
cons)
'() xs))
这样
> (bar '(1 2 3 4 5))
'(17 5 . 0)
> (baz '(1 2 3 4 5))
'(26 10 2)