Scheme中的流

来源:互联网 发布:最强大脑 小度 知乎 编辑:程序博客网 时间:2024/06/06 02:25

以赋值为工具对现实世界对象的状态进行模拟时基于以下原则:

1、用具有局部状态的计算对象去模拟现实世界中具有局部状态的对象;

2、用计算机里面随着时间的变化去表示真实世界中随着时间的变化,在计算机中,被模拟对象随着时间的变化是通过对那些模拟对象中的局部变量的赋值实现的。

在赋值模型中,需要复杂的环境模型为基础,并且需要考虑时间问题;在并发程序中所面临的主要困难也主要是赋值时刻的非确定性问题,多个并发进程对共享变量的计算结果严重依赖于对变量赋值的顺序。为了控制并发,不得不采用多种控制机制,如串行化,屏障同步等等。在处理时间和状态时,复制模型状态模型会遇到很大的困难以及复杂性。

为了缓和状态模拟中的复杂性,人们引进了一种流的数据结构,流是一种基于延时求值技术的序列结构,流处理可以模拟包含状态的系统,但是不需要利用赋值和变动数据,避免了赋值带来的内在缺陷。

序列作为组合程序的一种标准界面,能够构建功能强大的抽象机制,但是如果将序列简单地表示为表,那么这种标准界面的时间效率和空间效率常常会非常低下,因为将对序列的操作表示为对表的变换时,在程序运行的每个环节都必须去构造和赋值各种数据结构(他们可能规模巨大)。

       求某个区间里的素数之和的迭代风格程序如下:

(define(sum-primes a b)  (define (iter count accum)    (cond ((> count b) accum)          ((prime? count) (iter (+ count 1) (+count accum)))          (else (iter (+ count 1) accum))))  (iter a 0))

这个程序只需要维持两个变量值count和accum就可以得到最终的结果,而采用序列操作的程序:

(define(sum-primes a b)  (accumulate +              0              (filter prime?(enumerate-interval a b))))

实现的是同样的功能,但是首先需要enumerate-interval构造出包含区间[a  b]内的所有整数的表,然后filter才会用谓词prime?过滤出区间[a  b]内的所有素数的表,最后accumulate将素数表内的数累加起来得到最终结果。这个程序需要比第一个程序大得多的内存空间。空间效率太低。

       倘若需要通过序列方式计算从10000到1000000之间的第二个素数,这个低效情况显得更为突出:

 (car (cdr (filter prime?                   (enumerate-interval 100001000000))))

程序首先需要构造出区间[10000 1000000]内所有整数的表,然后筛选出其中所有的素数,最后再取出第二个素数。这种用表来表示序列的操作进行了许多无效的中间计算,导致程序效率极其低下。

       而流是一种非常巧妙的数据结构,不但能够使我们继续利用序列操作的标准界面,而且避免了将序列作为表去操作带来的负面影响。兼顾了程序的形式与效率,流的基本思想是。做出一种安排,只是部分地构造出流的结构,并将这样的部分结构送给使用流的结构,如果使用者需要访问这个流尚未构造出的那个部分,那么这个流就会自动地构造下去,但只是做出足够满足当时需要的那一部分,表面上看上去整个流就好像都存在一样。基于流的序列操作看上去都是在处理完整的序列,但是,实际上流的构造和流的使用是交错进行的,而这种交错对流的使用者而言又是完全透明的。

       为了使流的实现能自动地、透明地完成一个流的构造与使用的交错进行,使得对于流的cdr的求值要等到真正通过过程stream-cdr去访问它的时候再做,而不是在通过cons-stream构造流的时候做。作为一种数据抽象,流和表完全一样,它们的不同点在于元素的求值时间,对于常规的表来说,其car和cdr都是在构造时求值,而对于流,其cdr则是在选取的时候求值,

       流的延时求值是基于delay和force这两个过程实现的,(delay <exp>)的求值将不对表达式<exp>求值,而是返回一个延时对象,延时对象是对未来的某个时刻求值<exp>的允诺;而force则以一个延时对象作为参数,兑现delay的允诺,也就是对<exp>进行求值,如图所示:


       构造流的过程cons-stream是一个特殊形式,其定义将使

(cons-stream<a> <b>)

等价于

(cons <a>(delay <b>))

流的选择函数stream-car和stream-cdr可以定义如下:

(define(stream-car stream) (car stream))(define(stream-cdr stream) (force (cdr stream)))

stream-car选取序对的car部分,stream-cdr选取序对的cdr部分,并且求值其延时表达式,以获得这个流的后面部分。

       再以流的方式重新写求从10000到1000000之间的第二个素数的程序:

(stream-car  (stream-cdr   (stream-filter prime?                  (stream-enumerate-interval10000 1000000))))

类似地,首先stream-enumerate-interval构造出区间[10000 1000000]内的整数流:

(define(stream-enumerate-interval low high)  (if (> low high)      the-empty-stream      (cons-stream       low       (stream-enumerate-interval (+ low 1)high))))

可以看出,stream-enumerate-interval返回的流是由cons-stream构造的,返回一个如下的流:

(cons 10000 (delay(stream-enumerate-interval 10001 1000000)) )

也就是说,stream-enumerate-interval返回一个流,其car是10000,而其cdr则是这个流经过延时的其余部分,如果需要,可以枚举出这个流里的更多东西。

然后这个流被送去过滤出素数,这个过滤器stream-filter也是一个针对流的过程:

(define(stream-filter pred stream)  (cond ((stream-null? stream)the-empty-stream)        ((pred (stream-car stream))         (cons-stream (stream-car stream)                      (stream-filter pred                                    (stream-cdr stream))))        (else (stream-filter pred (stream-cdrstream)))))
stream-filter检查输入流的stream-car,此时就是10000,由于这个数并非素数,所以在else语句中进一步检查输入流的stream-cdr。由于此时输入流的stream-cdr是一个延时对象,所以会迫使系统对延时的stream-enumerate-interval进一步求值,得到结果流:

(cons 10001 (delay(stream-enumerate-interval 10002 1000000)) )

由于10001不是素数,所以进一步考察上述流的stream-cdr,由于此时输入流的stream-cdr也是一个延时对象,所以会迫使系统对延时的stream-enumerate-interval进一步求值,得到结果流:

(cons 10002 (delay(stream-enumerate-interval 10003 1000000)) )

然后考察10002是不是素数,如此下去,直至结果流为:

(cons 10007 (delay(stream-enumerate-interval 10008 1000000)) )

stream-filter检查输入流的stream-car,此时就是10007,这个数是素数,所以stream-filter返回一个流:

(cons 10007      (delay<span style="white-space:pre"></span>(stream-filter<span style="white-space:pre"></span> prime? <span style="white-space:pre"></span> (cons 10008              (delay<span style="white-space:pre"></span>(stream-enumerate-interval 10009 1000000))))))

然后过程stream-cdr访问这个流的cdr部分,迫使stream-filter对延时部分求值,由于10008不是素数,进一步迫使stream-enumerate-interval对延时部分求值,由于10009是素数,所以结果:

(cons 10009      (delay<span></span>(stream-filter<span></span> prime? <span></span> (cons 10010              (delay<span></span>(stream-enumerate-interval 10011 1000000))))))

被送给程序的最外层那个过程stream-car,得到最终结果10009.

总结一下:

1、流处理的每个阶段都仅仅活动到足够满足下一阶段所需要的程度,流计算总是本阶段在对流求出形如:cons <p1> (delay <p2>)形式的表达式之后,就将之返回给使用这个流的下一个阶段,例如过程

(stream-enumerate-interval 10000 1000000)
返回的流为:
(cons 10000 (delay(stream-enumerate-interval 10001 1000000)) )
,这个流又马上返回给stream-filter去过滤素数,stream-filter在求得第一个素数之后构造出形如:

(cons 10007      (delay<span></span>(stream-filter<span></span> prime? <span></span> (cons 10008              (delay<span></span>(stream-enumerate-interval 10009 1000000))))))

的流之后将其返回给stream-cdr继续求值,求值得到形如:

(cons 10009      (delay<span></span>(stream-filter<span></span> prime? <span></span> (cons 10010              (delay<span></span>(stream-enumerate-interval 10011 1000000))))))

的流之后返回给stream-car求值。

 

2、流是一种“需求驱动”的数据结构,总是在程序需要的时候才去求值,是一种构造与使用相互交替进行的数据结构。与常规表不同的是,流的cdr是在选取时才去求值的,而常规表是在构造时求值的。例如,stream-filter检查输入流:

(cons 10000 (delay (stream-enumerate-interval 10001 1000000)) )
的stream-car,是10000,由于这个数并非素数,进一步对输入流的stream-cdr求值,构造出一个新的流:

(cons 10001 (delay (stream-enumerate-interval 10002 1000000)) )

由于10001不是素数,所以进一步对新的流的stream-cdr求值,构造得到另外一个流:

(cons 10002 (delay (stream-enumerate-interval 10003 1000000)) )

然后考察10002是不是素数,如此下去,直至构造出流:

(cons 10007 (delay(stream-enumerate-interval 10008 1000000)) )

由此可见,流的构造和使用(求值)是交错进行的。

0 0
原创粉丝点击