【问题标题】:Prologs returns in haskellPrologs 在 haskell 中返回
【发布时间】:2014-01-28 03:58:01
【问题描述】:

我正在用 Haskell 重写我的 Prolog 程序,但我遇到了一个小问题,我该如何做这样的事情

myFunc(Field, Acc, Acc) :- 
    % some "ending" condition
    !.
myFunc(Field, Acc, Result) :-
    nextField(Field, Field2),
    test1(Field2,...),
    myFunc(Field2, Acc, Result).
myFunc(Field, Acc, Result) :-
    nextField(Field, Field2),
    test2(Ak, (X1, Y1)),
    myFunc(Field2, [Field2|Acc], Result).

在 Haskell 中?上面的代码正在检查一些条件并递归调用自身,所以最后我得到了特定字段的列表。关键是,如果某些条件(test1 或 test2)失败,它会返回到最后一点,它可以做出其他选择并执行。我如何在 Haskell 中实现类似的东西?

【问题讨论】:

  • 用栈模拟?
  • 如果您更详细地详细说明您尝试重新创建的算法,这将更容易提供帮助。到目前为止,我知之甚少。您是否正在递归输入数据结构?听起来您应该使用 Control.Monad 中的列表 monad 和守卫,但如果没有更多细节,我不能肯定地说。
  • @Davorak - 我有字段列表作为输入,因此我需要一个已删除字段的列表。有一些条件决定了我何时可以离开/删除字段,它们是 test1 和 test2(我在 myFunc 的第一种情况下离开字段并在第二种情况下删除)。通常,因此我需要满足我的条件的所有解决方案的列表,并且一个解决方案是一个已删除字段的列表。在序言中,我一次返回一个解决方案,然后我通过点击“;”来搜索另一个解决方案。但我想在haskell中我必须一次找到它们。我希望它能澄清一些疑问。

标签: haskell prolog


【解决方案1】:

要在 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]

甚至obserManyobserveAll

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]]

【讨论】:

  • 正是我需要的,但我真的没有可能安装额外的软件包。还有其他方法吗?
【解决方案2】:

您的评论有助于澄清一些问题,但对于您要查找的内容仍有一些疑问,因此这里是使用列表 monad 和守卫的示例。

import Control.Monad

myFunc lst = do
  e <- lst
  guard $ even e -- only allow even elements
  guard . not $ e `elem` [4,6,8] -- do not allow 4, 6, or 8
  return e -- accumulate results

在 ghci 中使用:

> myFunc [1..20]
[2,10,12,14,16,18,20]

【讨论】:

  • 我举个例子。假设我有矩阵 [[1,2],[1,2]] 作为输入,我将其转换为坐标列表: [(0,0),(1,0),(0,1) ,(1,1)]。假设 test1 是单个行或列中的两个值不能相同,而 test 2 是两个已删除的字段不能彼此相邻放置(在行或列中)。因此可接受的解决方案是 [(0,0),(1,1)] 和 [(1,0),(0,1)] (已删除字段的坐标)。现在,prolog 是如何做到的:第一个和第二个坐标满足 test1,但第三个坐标没有满足,所以 prolog 返回到第一个并“删除”它,然后继续。
  • 我不清楚你的算法是如何工作的。例如:“所以 prolog 正在返回到第 1 秒并“删除”它,”您没有说明为什么它会在此处回溯而不是删除第三个元素,这也满足测试一。
  • 嗯,是的,这实际上是错误的,对此感到抱歉。它将留下前两个,然后,第三个将不满足 test1 ,因此将其删除。但是再次test1不会满足第四个,所以它会尝试删除它但是test2会返回false,现在它会返回到第二个字段并删除它。最后我们会得到[(1,0),(0,1)]。
【解决方案3】:

我从来没有在 Haskell 中编程过——那么我会寻求你的帮助——但可以暗示一下

那个 Prolog 片段 - 我认为你有一个错字 - 应该是 myFunc(Field2, [(X1,Y1)|Acc], Result). 可以手动编译 - 在一个连续传递模式中。

让我们google 关于它(haskell 继续传递序言)。我先看看Wikipedia page:在Haskell 附近我们找到continuation monad

现在我们可以尝试在可执行的 Haskell 中翻译该 Prolog。这有意义吗?

【讨论】:

    【解决方案4】:

    您的代码到 Haskell 的文本翻译是:

    myFunc field acc = take 1 $              -- a cut
                         g field acc
      where
        g f a | ending_condition_holds = [a]
        g f a =
          ( nextField f       >>= (\f2 ->
            (if test1  ...                   -- test1 a predicate function
              then  [()]
              else  []  )     >>= (_ ->
            g f2 a         )))
          ++
          ( nextField f       >>= (\f2 ->
            test2  ...        >>= (\_ ->     -- test2 producing some results
            g f2 (f2:a)    )))
    

    【讨论】:

      猜你喜欢
      • 2010-12-04
      • 1970-01-01
      • 2023-03-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-09
      • 1970-01-01
      • 2014-11-24
      相关资源
      最近更新 更多