作为惰性列表的流

在惰性求值器中让流与列表合二为一:把 cons 实现为非严格的过程操作,列表操作即可直接用于无限流。

流,旧话重提

在第 11 课中,我们展示了如何把流(stream)实现成延迟的列表。我们引入了特殊形式(special form)delay 和 cons-stream,让我们能构造一个”承诺”(promise)——承诺以后去计算流的 cdr,而不是当场兑现。这种”需要更多求值控制权时就引入特殊形式”的通用技术固然好用,但有些笨拙。一来,特殊形式不像过程那样是一等对象,没法和高阶过程配合使用;二来,我们被迫把流造成一种新的数据对象,与列表相似却不相同,还得为流重新实现许多普通的列表操作(map、append 等等)。

惰性求值器中的流

有了惰性求值,流和列表就可以完全合一,不再需要特殊形式,也不需要两套列表和流操作。我们要做的只是安排好一切,让 cons 是非严格的。一种办法是扩展惰性求值器,允许非严格的原始过程,然后把 cons 实现成其中之一。更简单的办法是回想第 4 课:cons 压根没有必要作为原始过程来实现。我们可以把序对表示成过程:

(define (cons x y)
  (lambda (m) (m x y)))
(define (car z)
  (z (lambda (p q) p)))
(define (cdr z)
  (z (lambda (p q) q)))

基于这些基本操作,列表操作的标准定义对无限列表(也就是流)和对有限列表同样适用,流操作则可以直接实现成列表操作。下面是一些例子:

(define (list-ref items n)
  (if (= n 0)
      (car items)
      (list-ref (cdr items) (- n 1))))

(define (map proc items)
  (if (null? items)
      '()
      (cons (proc (car items))
            (map proc (cdr items)))))
(define (scale-list items factor)
  (map (lambda (x) (* x factor))
       items))
(define (add-lists list1 list2)
  (cond ((null? list1) list2)
        ((null? list2) list1)
        (else (cons (+ (car list1) (car list2))
                    (add-lists (cdr list1) (cdr list2))))))
(define ones (cons 1 ones))
(define integers (cons 1 (add-lists ones integers)))
;;; L-Eval input:
(list-ref integers 17)
;;; L-Eval value:
18

注意,这些惰性列表比第 11 课的流还要更惰性:不仅 cdr 是延迟的,car 也是。事实上,访问惰性序对的 car 或 cdr,都不一定需要强制求列表元素的值。只有真正需要时——比如要把元素作为原始过程的参数,或者要把它作为答案打印出来——值才会被强制。