Modest opinions  
by a humble autodidact

Workflow是F#的一个强大功能,既便不深入编译器,也不用stack hack,我们也可以用F#模拟出amb,从而更优雅得写出解决如回溯查找一类问题的程序。先从最简单的例子开始:

let nc0 =
    nondeter { let! x = Amb.choose [1;2;3]
               return x}

这段程序的含义为,x取1,2,3中任意一个值,然后返回x的值。得到nc0的类型为seq<int>,代表所有可能的计算结果的集合,在这段简单的程序里就是1、2和3。

如果有多个不确定变量呢: 

 

let nc1 =

    nondeter { let! x = Amb.choose [1;2]
               let! y = Amb.choose [3;4]
               return (x,y)

             }

< p>那么当x取值为1的时候,y第一次取值为3,结果成为nc1的第一值。然后x保持不变,y取另一个值4。此时y可能的值已经用完,又回到对x值 的选择,x取下一个值2,y重新开始取值3。最后y取4,x,y所有可能的取值用光。nc1的结果为(1, 3)、 (1, 4)、 (2, 3)、 (2, 4)。

 

可以用Amb.require限制不确定变量的取值满足一定条件:

 

let nc2 =
    nondeter{ let! y = Amb.choose [10;20;30]
              let! x = Amb.choose [1;2;3]
              let! z = Amb.choose [3;4;5]
              do Amb.require(x+y/10>z)

              return x+y+z}

这段程序的含义是:对所有的y∈{10, 20, 30} x∈{1,2,3} z∈{3,4,5},只要有满足x+y/10>z的x、y、z取值,就返回x+y+z。

< p>以上三个例子好像跟用for循环没有什么区别,的确,amb的真正威力在于每次进行不确定取值时,程序(确切的说是程序执行过程的堆栈)自动回 到上次取值时的状态,因此程序执行到不确定取值处时好像是分叉了一般,每个分支取一个值接着执行。为了在F#中模拟出这种“保持堆栈状态”的语言,我们要 求所有的变量用let <id> = Amb.preserve <value>的形式初始化,得到<id>的类型为ref<type of "value">。需要引用变量<id>的地方写成!<id>,需要改变 <id>的值时写do <id>:= <new value>。看下面的程序:

 

let nc7 =
    nondeter {let c = Amb.preserve 0
              while !c < 2 do
                  let! x = Amb.choose [0; 1]
                  do printfn "c=%d x=%d" !c x  
                  do c:= !c+1
             }

得到nc7的类型为seq<unit>,原因是nodeter内没有return,所以不返回任何东西,只利用其运行过程中的副效应(do printfn ...),遍历nc7,控制台上打印出:

c=0 x=0
c=1 x=0
c=1 x=1
c=0 x=1
c=1 x=0

c=1 x=1

< p>说明一下原理:Amb.choose在选择一个不确定值之前,会把所有用Amb.preserve初始化的变量存一个快照;每选择一个值的时 候,Amb.choose会把所有用Amb.preserve初始化的变量恢复到之前快照的值,以此模拟出保存堆栈的效果。

 

< p>这里是一个稍稍有些真实感的例子,我们用F#的模拟amb解决 这个问题

 

Code

最后是实现amb模拟的代码,即nondeter的定义:

Code

 

posted on 2008-08-05 23:46  yushih  阅读(437)  评论(0编辑  收藏  举报