Haskell Monad函数

时间:2011-12-06 06:52:34

标签: haskell puzzle monads ghc

我正在阅读一个Haskell教程,并给出了这段代码来处理国际象棋中的骑士:

import Control.Monad

type KnightPos = (Int,Int)

moveKnight :: KnightPos -> [KnightPos]  
moveKnight (c,r) = do  
    (c',r') <- [(c+2,r-1),(c+2,r+1),(c-2,r-1),(c-2,r+1)  
               ,(c+1,r-2),(c+1,r+2),(c-1,r-2),(c-1,r+2)  
               ]  
    guard (c' `elem` [1..8] && r' `elem` [1..8])
    return (c',r')

in3 :: KnightPos -> [KnightPos]
in3 start = return start >>= moveKnight >>= moveKnight >>= moveKnight

canReachIn3 :: KnightPos -> KnightPos -> Bool
canReachIn3 start end = end `elem` in3 start 

练习是修改功能,以便canReachIn3告诉你如果可以到达最终位置,你可以采取什么动作。

本教程基本上没有练习,所以我遇到了这样的基本问题......我正在考虑将所有3个函数的返回值更改为[[KnightPos]],其中1个大列表包含每个列表的列表可能的动作顺序。这可能会让moveKnight有一个[KnightPos]参数而不是KnightPos参数,这会打败monad的整个点吗?

非常感谢任何帮助/想法,谢谢。

4 个答案:

答案 0 :(得分:7)

如果您发现普通的旧列表操作对您来说更自然,那么在考虑此代码时,从Monad概念中退一步可能会有所帮助。因此,您可以重写示例代码(通过一些清理以便清晰起见):

type KnightPos = (Int,Int)

moveKnight :: KnightPos -> [KnightPos]  
moveKnight (c,r) = filter legal candidates where
    candidates = [(c+2,r-1), (c+2,r+1), (c-2,r-1), (c-2,r+1),
                  (c+1,r-2), (c+1,r+2), (c-1,r-2), (c-1,r+2)]
    legal (c',r') = c' `elem` [1..8] && r' `elem` [1..8]

in3 :: KnightPos -> [KnightPos]
in3 start = concatMap moveKnight (concatMap moveKnight (moveKnight start))

canReachIn3 :: KnightPos -> KnightPos -> Bool
canReachIn3 start end = end `elem` in3 start

秘密酱在concatMap。正如您可能已经知道的那样,它与>>= monad中的List同义,但现在将其视为将KnightPos -> [KnightPos]类型的函数映射到列表上可能更有帮助{ {1}}生成列表[KnightPos],然后将结果展平为单个列表。

好的,现在我们暂时放弃了monad,让我们回顾一下这个难题......让我们说你的初始[[KnightPos]]KnightPos,你想跟踪所有可能的从那个位置移动的序列。因此,定义另一种类型的同义词:

(4,4)

然后你希望type Sequence = [KnightPos] 对这些序列进行操作,找到序列中最后一个位置的所有可能移动:

moveKnight

从序列moveKnight :: Sequence -> [Sequence] 开始,我们得到序列列表:[(4,4)]。然后我认为您需要对[[(4,4), (6,3)], [(4,4), (6,5)], [(4,4), (2,3)], ... ]进行的唯一更改是相应地修改其类型签名:

in3

我不认为实际的实施会发生变化。最后,您需要in3 :: Sequence -> [Sequence] 看起来像:

canReachIn3

我故意将实施细节留在这里,因为我不想完全破坏你的谜题,但我希望我在这里说明没有什么特别的“特殊”关于列表,列表或其他什么。我们真正做的就是用类型为canReachIn3 :: KnightPos -> KnightPos -> [Sequence] 的新函数替换KnightPos -> [KnightPos]类型的函数 - 几乎相同的形状。因此,使用任何感觉自然的方式填写每个函数的实现,然后一旦它工作,返回monadic样式,用Sequence -> [Sequence]替换concatMap,依此类推。

答案 1 :(得分:3)

我建议让KnightPos数据结构不仅能够保存当前的药水,还能保存它来自的KnightPos

data KnightPos = KnightPos {history :: Maybe KnightPos, position :: (Int,Int) }

然后,您需要实现Eq和Show类,并修复moveKnight,以便它返回此结构并正确设置其历史记录:

moveKnight :: KnightPos -> [KnightPos]  
moveKnight p@KnightPos{position = (c,r)} = do  
    (c',r') <- [(c+2,r-1),(c+2,r+1),(c-2,r-1),(c-2,r+1)  
               ,(c+1,r-2),(c+1,r+2),(c-1,r-2),(c-1,r+2)  
               ]  
    guard (c' `elem` [1..8] && r' `elem` [1..8])
    return $ KnightPos (Just p) (c',r') 

确保您了解此功能的最后一行。

函数in3现在应该无需修改即可运行。我编写了一个新函数pathIn3 :: KnightPos -> KnightPos -> [[KinghtPos]],它在3个monadic语句中返回从startend的所有可能路径。

扰流警报

  

pathIn3 :: KnightPos - &gt; KnightPos - &gt; [[KnightPos]]
    pathIn3 start end = do
      p&lt; - in3 start
      守卫(p ==结束)
      return $ getHistory p

答案 2 :(得分:2)

“这可能会让moveKnight有一个[KnightPos]参数而不是KnightPos参数...” 不,你不想这样做。保持功能尽可能基本。

“......哪会打败monad的整个点?” 好吧,如果您想要所有订单的移动,那么您不会在每次移动中使用join中隐含的>>=。您可以定义一个函数,从给定的起始点返回长度为 n 的所有路径的列表,我们可以将这些路径实现为传递的位置列表,出于效率原因,它们的顺序相反。

waysFrom :: KnightPos -> Int -> [[KnightPos]]

首先进行一次移动(或零)

waysFrom start 0 = [[start]]
waysFrom start 1 = map (\t -> [t, start]) $ moveKnight start

然后对于任意数量的移动,此时您可以再次使用>>=来加入类似于trie的结构,这是由于直接递归到移动列表列表所致:

waysFrom start n = waysFrom start (n-1) >>= (\w -> [t:w | t<-moveKnight$head w])

可能有一种更好的方法可以做到这一点,但它实际上并没有“打败”monad的整个点。

答案 3 :(得分:1)

我正在阅读教程,我的解决方案是更改功能in3canReachIn3

in3 :: KnightPos -> [[KnightPos]]
in3 start = do
    first <- moveKnight start
    second <- moveKnight first
    third <- moveKnight second
    return [start, first, second, third]

canReachIn3 :: KnightPos -> KnightPos -> [[KnightPos]]
canReachIn3 start end = filter (elem end) $ in3 start

in3的结果包含列出所有可能的路径。