[英]Representing a queue as a procedure with local state
第 90 页的第 2.1.3 节用一个非常清晰的例子解释了语言中的一流函数使函数本身和数据从不同的角度来看是同一件事,或者,引用这本书:
将过程作为对象进行操作的能力自动提供了表示复合数据的能力。
在第 266 页, 第 3.3.2 节中的练习 3.22 提出以下内容
我们可以将队列构建为具有本地状态的过程,而不是将队列表示为一对指针。 本地状态将由指向普通列表开头和结尾的指针组成。 因此,
make-queue
过程将具有以下形式(define (make-queue) (let ((front-ptr ...) (rear-ptr ...)) <definitions of internal procedures> (define (dispatch m) ...) dispatch))
完成
make-queue
的定义并使用此表示提供队列操作的实现。
我很容易想到了以下内容(我使用名称the-list
和last-pair
而不是front-ptr
和rear-ptr
因为我发现它更清晰,在这种情况下):
(define (make-queue)
(let ((the-list '())
(last-pair '()))
(define (dispatch m)
(cond ((eq? m 'empty) (null? the-list))
((eq? m 'front) (if (null? the-list)
(error "can't take front of empty list")
(car the-list)))
((eq? m 'ins) (lambda (e)
(if (null? the-list)
(begin (set! the-list (list e))
(set! last-pair the-list))
(begin (set-cdr! last-pair (list e))
(set! last-pair (cdr last-pair))))
the-list))
((eq? m 'del) (begin
(if (null? the-list)
(error "can't delete from emtpy list")
(set! the-list (if (pair? the-list) (cdr the-list) '())))
the-list))
((eq? m 'disp) (display the-list)) ; added this for convenience
(else "error")))
dispatch))
(define (empty-queue? q) (q 'empty))
(define (front-queue q) (q 'front))
(define (insert-queue! q e) ((q 'ins) e))
(define (delete-queue! q) (q 'del))
(define (display-queue q) (q 'disp))
这似乎工作得相当好……
……除了一个关键点!
在第 3.3.2 节的开头,两个所需的 mutators(它们是队列接口的一部分)是这样定义的(我的重点):
(insert-queue! <queue> <item>)
将项目插入队列的尾部,并将修改后的队列作为其值返回。
(delete-queue! <queue>)
删除队列前面的项目并将修改后的队列作为其值返回,如果队列在删除之前为空,则发出错误信号。
我的解决方案不遵守定义的那些部分,因为两者都是insert-queue!
并delete-queue!
正在返回the-list
,这是一个裸列表,是队列接口的一个实现细节。 确实,我的解决方案不支持这样的事情
(define q (make-queue)) ; ok
(insert-queue! (insert-queue! q 3) 4) ; doesn't work
(delete-queue! (delete-queue! q)) ; doesn't work
而我认为应该。
我想解决方案应该看到delete-queue!
和insert-queue!
返回dispatch
函数的变异版本。
我怎么做?
没必要。 简单定义
(define (insert-queue! q e)
((q 'ins) e)
q)
(define (delete-queue! q)
(q 'del)
q)
但是,设计并不干净,因为这些队列不是持久的。 新版本和旧版本共享相同的底层缓冲区(列表)。 不再保留旧版本,仅保留当前版本。
所以我们不会返回一个新的、修改过的队列; 我们返回同一个已经变异的队列。 从概念上讲,就是这样。 在较低的级别上,我们返回相同的调度过程,它是同一个闭包的一部分,该闭包为已变异的内部缓冲区保存相同的内部绑定。
顺便说一句,使用 head sentinel 技巧,如果你从例如(list 1)
而不是'()
开始,通常会导致更简化、更清晰的代码。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.