带有延续传递样式(不带call/cc)
如果您实现一个使用显式延续传递样式而不是首先使用call/cc 的版本,则可能更容易理解此示例。在这种情况下,让我们从map 的延续传递版本开始:
(define (kmap fn list k)
(if (null? list)
(k list)
(fn (car list)
(lambda (head)
(kmap fn
(cdr list)
(lambda (tail)
(k (cons head tail))))))))
(define (identity x) x)
(kmap (lambda (x k) (k (+ 1 x))) '(1 2 3 4) identity)
;=> (2 3 4 5)
如果您不熟悉连续传球风格,这可能会让您有点头晕,但也不会太难。请记住,kmap 和 fn 在末尾都带有一个附加参数,应该使用“结果”调用。因此,当我们用(car list) 调用fn 时,我们还向它传递了一个过程(lambda (head) ...),它负责为我们处理其余的映射。映射的其余部分再次根据kmap 定义。对kmap 的每次调用都需要一个最终的延续,该延续期望接收将fn 映射到列表上的结果。
现在,既然我们可以看到如何使用延续传递样式实现映射,我们可以使用它编写一个迭代器生成过程。过程iterator 接受一个列表并返回一个过程,我们可以调用该过程来获取list 的每个元素。
(define (iterator list)
(define (next k)
(kmap (lambda (item more-next)
(set! next more-next)
(k item))
list
(lambda (results)
(k 'done))))
(lambda ()
(next identity)))
> (define get (iterator '(1 2)))
> (get)
1
> (get)
2
> (get)
done
> (get)
done
> (define get (iterator '(a b c)))
> (get)
a
> (get)
b
> (get)
c
> (get)
done
> (get)
done
这里的诀窍是我们定义了一个本地过程next。当list 的每个元素被处理为将处理list 剩余部分的过程时,它使用一个重新定义 next 的过程调用kmap。重新定义next 后,它使用元素调用k。传递给kmap 的最终延续实际上忽略了传递给它的结果,而只是使用符号done 调用k。我们从iterator返回的不是next 的值,而是一个调用next 并延续identity 的过程。这里的间接意味着我们总是用identity 调用next 的latest 值。传递identity 作为延续意味着我们只取回列表元素。
与call/cc
现在我们知道如何在没有call/cc 的情况下做到这一点,我们可以更好地了解如何使用call/cc 来做到这一点。回忆一下问题中的定义:
(define (itr lst)
(define (state k)
(for-each (lambda (item)
(call/cc (lambda (h)
(set! state h)
(k item))))
lst)
(k 'done))
(define (generator)
(call/cc (lambda (k) (state k))))
generator)
返回生成器
首先要注意
(define (generator)
(call/cc (lambda (k) (state k))))
generator
可以简化为
(lambda () (call/cc (lambda (k) (state k))))
这就是我们在实施中所做的。当您从 REPL 调用它时,k 想要做的就是获取该值并返回它(并打印它)。在我们的版本中,我们通过简单地返回原样来近似它。也就是说,我们使用identity,我们使用名称next而不是state。所以
(lambda () (call/cc (lambda (k) (state k))))
就像
(lambda () (next identity))
state(或next)过程
剩下的
(define (state k)
(for-each (lambda (item)
(call/cc (lambda (h)
(set! state h)
(k item))))
lst)
(k 'done))
也与我们所做的非常相似。我们不使用kmap 和带有两个参数(项目和延续)的fn,而是使用for-each,它接受单个参数(项目)的过程,在该过程中,我们使用call/cc抓住延续。所以
(for-each
(lambda (item)
(call/cc (lambda (h)
...
就像
(kmap (lambda (item h)
...
for-each 不需要最终的延续参数,所以我们不能传递结果忽略(lambda () (k 'done))。相反,我们只是在for-each 调用之后调用(k 'done) 。也就是说,
(for-each fn list)
(k 'done)
就像
(kmap fn
list
(lambda (result)
(k 'done)))
保存程序状态
在这两种实现中,您在某种意义上都在“保存程序状态”。您正在保存的重要状态是将继续迭代列表的状态。