要在 Haskell 中以表达方式对 Prolog 计算进行建模,您需要一个回溯 monad。这可以使用LogicT monad 轻松完成。您的示例可以转化为以下内容:
import Control.Monad.Logic
myFunc :: Int -> [Int] -> Logic [Int]
myFunc field acc = ifte (exitCond field acc) (\_-> return acc) $
(do f <- nextField field
guard $ test1 f
myFunc f acc)
`mplus`
(do f <- nextField field
guard $ test2 f
myFunc f (f:acc))
假设函数和谓词的实现如下:
nextField i = return (i+1)
test1 f = f < 10
test2 f = f < 20
exitCond f a = guard (f > 15)
您使用mplus 来组合Logic 计算,这样如果一个计算失败,它会回溯并尝试另一个。 ifte 只是一个软切(logict 中没有硬切,尽管我相信它很容易实现,因为 logict 是基于延续的)在退出条件为真时退出。您按如下方式运行计算:
Main> runLogic (myFunc 1 []) (\a r -> a:r) []
[[16,15,14,13,12,11,10],[16,15,14,13,12,11,10,9],[16,15,14,13,12,11,10,8]...
runLogic 采用Logic 计算、延续和延续输出的初始值。在这里,我刚刚传递了一个延续,它将所有结果累积到一个列表中。与 Prolog 示例不同,上面将回溯并获得所有解决方案,因为我们使用了软切割而不是硬切割。要在获得第一个解决方案后停止回溯,您可以使用once:
Main> runLogic (once $ myFunc 1 []) (\a r -> a:r) []
[[16,15,14,13,12,11,10]]
您也可以使用observe 仅观察第一个解决方案,而无需传递延续:
Main> observe (myFunc 1 [])
[16,15,14,13,12,11,10]
甚至obserMany和observeAll:
observeMany 5 (myFunc 1 []) --returns the first 5 solutions
observerAll (myFunc 1 []) --returns a list of all solutions
最后,您需要安装logict 包才能使上述代码正常工作。使用cabal install logict进行安装。
编辑:在 cmets 中回答您的问题
是的,您无需安装logict 即可执行类似操作。虽然一个专门的回溯 monad 可以让事情变得不那么复杂,并且清楚地表明你想要做什么。
要模拟上面的 logict 示例,您只需要 [] monad
myFunc :: Int -> [Int] -> [[Int]]
myFunc field acc | exitCond field acc = return acc
myFunc field acc = do
let m1 = do
f <- nextField field
guard $ test1 f
myFunc f acc
m2 = do
f <- nextField field
guard $ test2 f
myFunc f (f:acc)
in m1 `mplus` m2
nextField i = return $ i + 1
exitCond i a = i > 15
test1 i = i < 10
test2 i = i < 20
你可以这样运行它:
Main> myFunc 1 []
[[16,15,14,13,12,11,10],[16,15,14,13,12,11,10,9],[16,15,14,13,12,11,10,8]...
你也可以像以前一样选择你想要的解决方案:
Main> head $ myFunc 1 []
[16,15,14,13,12,11,10]
Main> take 3 $ myFunc 1 []
[[16,15,14,13,12,11,10],[16,15,14,13,12,11,10,9],[16,15,14,13,12,11,10,8]]
但是,您将需要 Cont monad,因此需要 ListT monad 来实现如 Prolog 示例中那样的硬剪切,这在上面的 logict 示例中不可用:
import Control.Monad.Cont
import Control.Monad.Trans.List
myFunc :: Int -> ListT (Cont [[Int]]) [Int]
myFunc field = callCC $ \exit -> do
let g field acc | exitCond field acc = exit acc
g field acc =
let m1 = do
f <- nextField field
guard $ test1 f
g f acc
m2 = do
f <- nextField field
guard $ test2 f
g f (f:acc)
in m1 `mplus` m2
g field []
和Prolog一样,这最后一个例子在exitCond满足后不会再回溯:
*Main> runCont (runListT (myFunc 1)) id
[[16,15,14,13,12,11,10]]