3.5.4 流与延迟评估

3.5.4 流与延迟评估
在上一个部分中的结尾部分的integral程序显示了我们能如何使用流来对
包括了反馈的循环的信号处理系统进行建模。加法器的反馈循环显示在图3.32中,
用Integral程序的内部的流的定义来进行了建模:

 (define int
             (cons-stream inital-value
                                   (add-streams (scale-stream integrand dt)
                                                        int)))

解释器处理这样的隐式的定义的能力依赖于被集成到cons-stream中的delay.
没有了这个delay,解释器在解释cons-stream的参数之前,不能组装int,
因为cons-stream要求int已经被定义。总之,delay是我们对包括了循环的
信号处理系统进行建模的关键。没有了这个delay,我们的模型将不得不公式化,
为了在输出产生之前,让任何的信号处理组件的输入能被完全地解释。这有
非法的循环。

不幸的是,带有循环的系统的流模型可能要求delay的使用,脱离了cons-stream提供的
隐式的delay.例如,图3.34显示了一个信号处理系统,来解决微分方程dy/dt=f(y),f是一个
给定的函数。图上显示了一个映射组件,它把f应用到它的输入信号上,在一个反馈的循环中连接到
一个积分器,与实际使用的解这个方程的模拟的计算机的电路非常相似的方式。

图3.34  一个模拟的计算机的电路来解微分方程dy/dt=f(y)

假定,我们为y,给定一个初始化的值y0,我们将试图使用这个程序对这个系统
建模

(define (solve   f  y0  dt)
    (define  y (integral  dy  y0 dt))
    (define  dy (stream-map   f  y))
    y
)

这个程序没有工作,因为solve的第一行调用integral 要求输入dy 被定义
直到solve的第二行才定义。

另一方面,我们的定义的意图是智能的,因为我们能,原则上,开始生成y的流
而没有知道dy.的确,integral和许多的其它的流的操作与cons-stream有相似的属性,
我们能根据参数的部分信息来生成答案的一部分。对于integral,输出流的第一个元素
是指定的initial-value. 因此,我们能生成输出流的第一个元素,而没有解释dy的值。
一旦我们知道了y的第一个元素,在solve的第二行中的stream-map能开始工作,来生成
dy的第一个元素,它生成y的下一个元素,等等。

利用这个思想,我们将重定义integral,来期待流成为一个推迟的参数。Integral仅当要生成
输出流的超过第一个元素时,将强制解释被积函数。

(define (integral delayed-integrand initial-value dt)
   (define int
             (cons-stream inital-value
                    (let ((integrand    (force   delay-integrand)))            
                             (add-streams (scale-stream integrand dt)
                                                        int))))
int)

现在我们能实现我们的solve程序,通过使用在y的定义中的dy的推迟解释。

(define (solve   f  y0  dt)
    (define  y (integral  (delay dy)  y0 dt))
    (define  dy (stream-map   f  y))
    y
)

总之,integral的每个调用者现在必须推迟被积函数参数。我们能演示solve程序的工作,
以e约等于2.718的值,计算y=1 ,微分方程 dy/dt=y,初始条件为y(0)=1:

(stream-ref  (solve (lambda (y)  y)   1  0.001)   1000)
2.716924

练习3.77
上面的integral程序使用了与3.5.2部分中的整数的无限流的隐式的定义的相似的方法。
可选的是,我们能给出一个integral的定义,与integers-starting-from更像的定义。

(define (integral integrand initial-value dt)
      (cons-stream initial-value
                          (if   (stream-null? integrand) 
                                     the-empty-stream 
                                         (integral   (stream-cdr integrand)
                                                         (+  (* dt (stream-car integrand))
                                                                init-value)
                                                         dt)
                          )  ))

当在系统中使用循环时,这个程序与Integral的初始版本有相同的问题。修改
程序,让integrand作为一个推迟的参数,因此而能用于solve程序中。

练习3.78
考虑设计一个信号处理系统,来研究二阶的线性微分方程:
d2y/dt2-a(dy/dt)-by=0
输出的流,建模Y,是被一个包括了循环的网络来生成的。这是因为d2y/dt2的值依赖于y和dy/dt
的值,而它们都被d2y/dt2的积分值决定。我们看到的图被编码显示在图3.35
写一个程序,solve-2nd,它有参数是常数a,b,dt,为y 和dy/dt而准备的初始值 y0,dy0,
生成y的连续的值的流。

练习3.79
泛化练习3.78中的程序solve-2nd,让它能用来解通用的二阶微分方程d2y/dt2=f(dy/dt,y)

练习3.80
一个序列化的RLC电路由一个电阻器,一个电容器,一个线圈顺序地相联而成。
如果RLC是电阻器,线圈,电容器,那么它们之间的电压与电流的关系被描述为
如下的方程:

vR=iR*(R)
vL=(L)*(diL/dt)
iC=(C)*(dvC/dt)

并且电路的连接显示了如下的关系:

iR=iL=-iC
vC=vL+vR

组合这些方程,显示了电路的状态,被描述为如下的微分方程:

(dvC/dt)=-iL/C
(diL/dt)=1/L*vC-R/L*iL

信号流的图显示了这个微分方程的系统。

写一个程序RLC,它的参数是电路的RLC,和时间的增量dt,
以练习3.73中的RC程序相似的方式,生成一个程序,这个程序有参数
是状态变量的初始化值,vc0,iL0,生成Vc,iL的状态的流的数对。
使用RLC,生成流的数对,来建模一个序列化RLC的电路的行为。R=1欧姆,
C=0.2伏特,L=1库仑,dt=0.1秒。初始化值iL0=0,vC0=10.

*正常序解释
在这部分中的例子,显示了delay 和force的显式使用如何 提供了巨大的编程灵活性,
但是相同的例子也显示了这如何能够让我们的程序更加地复杂。
我们的新的Integral程序,例如,给我们对有循环的系统进行建模的能力,但是我们现在
必须记住的是Integral应该调用一个推迟的integrand,每个使用Integral的程序必须注意到这
一点。在效果上,我们已经创造了程序的两个类型,即普通程序,和有推迟参数的程序。
总之,创建程序的单独的类型,也将强制我们创建高阶程序的单独的类型。

避免需要两种不同的类型的程序方式之一是让所有的程序都有推迟的参数。我们采用一个解释
的模型,它的所有的参数对于程序而言,都是自动化地推迟与强制,且仅在实际需要使用参数
时强制解释。这将把我们的语言转换成使用正常序的解释。正如我们在1.1.5部分中描述的替换
模型。转换到正常序,提供了一个统一的,优雅的方式来简化推迟解释的使用,如果我们仅关注
流处理,这是很自然的策略。在4.2部分中,在我们研究了解释器后,我们将看到如何以这种方式
来转换语言。不幸的是,在程序调用时包括推迟,弱化了我们对依赖于事件顺序的程序的设计能力,
例如使用赋值语句的,有交互数据的,执行输入和输出的程序等等。正如练习3.51 和3.52的显示的那样,
甚至在cons-stream中的单个delay也能引起巨大的混乱。正如大家所知的那样,交互性和推迟解释
在编程语言中不能很好地混合在一起,对它们进行分别处理是一个研究的热点区域。

猜你喜欢

转载自blog.csdn.net/gggwfn1982/article/details/82838208