;;;
;;; amb
;;;
(define (amb-now-fail)
"amb exhauted!")
(define-syntax amb
(syntax-rules ()
((_ e ...) (let ((prev-fail amb-now-fail))
(call/cc (lambda (sk)
(call/cc (lambda (fk)
(set! amb-now-fail
(lambda ()
(set! amb-now-fail prev-fail)
(fk 'fail)))
(sk e)))
...
(prev-fail)))))))
amb 的 Scheme 实现
2.1 初始化
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)
2.2 amb 的 syntax-rules 实现
我们用 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)))))))
2.3 分析
有些不容易看懂,实际上它的功能就是把
(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 就返回一个值。
每一个分支在第一次执行时,有两项工作:
(1) 把当前的 amb-fail 设置为一个函数。这个 *内部函数* 的作用就是把 amb-fail 的值恢复到进入 amb 以前的值:
(lambda ()
(set! amb-fail prev-amb-fail)
(fk 'fail))
(2) 立即通过 amb 表达式的 continuation(sk) 返回自己的分支的值。从而引起 amb 表达式中途返回。
注意,每一个分支执行时都会引起 amb 立即返回。后面的分支都还没有执行!
2.3.1 举例
(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 表达式没有“失败”。
2.3.2 简化的实现
其实上面的 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)))))))