带有继续传递样式(不带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
最后都需要一个额外的参数,应该用“结果”调用。因此,当我们调用fn
with时(car list)
,我们还向它传递了一个过程,该过程(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
。它调用kmap
一个过程,该过程重新定义 next
的每个元素何时list
被处理为将处理 的其余部分的过程list
。重新定义后,它与元素一起next
调用。k
传递给它的最后一个延续kmap
实际上忽略了传递给它的结果,只是k
用符号调用done
。我们返回的iterator
不是 的值,而是一个用 continuationnext
调用的过程。这里的间接意味着我们总是调用with的最新值。通过next
identity
next
identity
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
两个参数(项目和延续)的 and a,而是使用for-each
带有单个参数(项目)的过程,并且在该过程中,我们使用它call/cc
来获取延续。所以
(for-each
(lambda (item)
(call/cc (lambda (h)
...
就像
(kmap (lambda (item h)
...
for-each
不需要最终的延续参数,所以我们不能通过 result-ignoring (lambda () (k 'done))
。相反,我们只是在调用之后(k 'done)
调用for-each
。那是,
(for-each fn list)
(k 'done)
就像
(kmap fn
list
(lambda (result)
(k 'done)))
保存程序状态
在这两种实现中,您在某种意义上都在“保存程序状态”。您要保存的重要状态是将继续迭代列表的状态。