amb-fail 是最近一个失败的分支设置的函数。如果执行没有参数的 (amb) 就会转到这个 amb-fail.
这个例子里,我们把 amb-fail 被初始化为打印 "amb tree exhausted"。
(define amb-fail '*) (define initialize-amb-fail (lambda () (set! amb-fail (lambda () (error "amb tree exhausted"))))) (initialize-amb-fail)
我们用 R5RS 的 syntax-rules 来实现 amb 操作符:
(define-syntax amb (syntax-rules () ((amb alt ...) (let ((prev-amb-fail amb-fail)) (call/cc (lambda (sk) (call/cc (lambda (fk) (set! amb-fail (lambda () (set! amb-fail prev-amb-fail) (fk 'fail))) (sk alt))) ... (prev-amb-fail)))))))
有些不容易看懂,实际上它的功能就是把
(amb #f #t)
这样的输入,转换成:
(let ((prev-amb-fail amb-fail)) (call/cc (lambda (sk) ; branch 1 (call/cc (lambda (fk) (set! amb-fail (lambda () (set! amb-fail prev-amb-fail) (fk 'fail))) (sk #f))) ; branch 2 (call/cc (lambda (fk) (set! amb-fail (lambda () (set! amb-fail prev-amb-fail) (fk 'fail))) (sk #t))) (prev-amb-fail))))
表达式先把 amb-fail 的值保存在局部变量 prev-amb-fail 里,这 样当整个 amb 表达式失败时,它可以通过 prev-amb-fail 通知上 一个 amb 表达式改变它的值。
整个 amb 表达式的 continuation 存放在 sk 里。对于每一个参数, 使用了一个 call/cc 得到它的 continuation. 并且保存在 fk 里。 我们把这些参数对应的 call/cc 暂且叫做 *分支* 好了。看上面的 "; branch 1" 和 "; branch 2".
当某一个分支得到一个值,它就通过整个 amb 的 continuation(sk) 把这个值返回出去。这样 amb 就返回一个值。
每一个分支在第一次执行时,有两项工作:
把当前的 amb-fail 设置为一个函数。这个 *内部函数* 的作 用就是把 amb-fail 的值恢复到进入 amb 以前的值:
(lambda () (set! amb-fail prev-amb-fail) (fk 'fail))
立即通过 amb 表达式的 continuation(sk) 返回自己的分支 的值。从而引起 amb 表达式中途返回。
注意,每一个分支执行时都会引起 amb 立即返回。后面的分支都还 没有执行!
(if (amb #f #t) 1 (amb))
就用最开头的那个最简单的例子,这样容易理解:
(let ((prev-amb-fail amb-fail)) (call/cc (lambda (sk) ; branch 1 (call/cc (lambda (fk) (set! amb-fail (lambda () (set! amb-fail prev-amb-fail) (fk 'fail))) (sk #f))) ; branch 2 (call/cc (lambda (fk) (set! amb-fail (lambda () (set! amb-fail prev-amb-fail) (fk 'fail))) (sk #t))) (prev-amb-fail))))
第一个 amb 被展开,就成了上面那个样子。#f 和 #t 是两个分支。 然后 #f 对应的分支将被运行。这个分支的 call/cc 把 amb-fail 绑定到自己的内部函数,然后马上使用
(sk #f)
返回分支的值。
接着 if 得到这个值,从而引起第二个没有参数的 (amb) 的执行。 这就是一个“失败”。(amb) 的执行没有参数,所以没有分支。它被 展开成:
(let ((prev-amb-fail amb-fail)) (call/cc (lambda (sk) (prev-amb-fail))))
它马上就会执行最下面的
(prev-amb-fail)
而 prev-amb-fail 在进入这个 (amb) 的时候被绑定到了 amb-fail, 也就是最近一个失败函数。这里 amb-fail 其实就是第一个 amb 表 达式的 #f 分支设置的。
所以,我们将执行 #f 的分支设置的 amb-fail 函数。这就是 #f 分 支的内部函数,它先把 amb-fail 的值设置成 prev-amb-fail 也就 是进入 (amb #f #t) 以前的值,然后使用 (fk fail) 返回 fail 到分支的 continuation.
接着 (amb #f #t) 的第二个分支开始执行。它在设置好 amb-fail 为自己的内部函数之后,返回了 #t 给 if. 那么 if 就会返回 1. 使得整个 if 表达式没有“失败”。
其实上面的 amb 实现似乎复杂了一些。我们可以简化如下:
(define-syntax amb (syntax-rules () ((amb alt ...) (let ((prev-amb-fail amb-fail)) (call/cc (lambda (sk) (call/cc (lambda (fk) (set! amb-fail fk) (sk alt))) ... (prev-amb-fail)))))))