Haskell Monad函数

我正在浏览一个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 ,然后这会击败monads的全部点吗?

任何帮助/想法将不胜感激,谢谢。


如果您发现简单的旧列表操作对您来说更自然,那么在思考此代码时,它可能有助于退出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 。 正如你可能已经知道的那样,它与List monad中的>>=是同义词,但现在可能更有帮助,将它看作是将KnightPos -> [KnightPos]类型的函数映射到列表[KnightPos]以产生一个列表列表列表[[KnightPos]] ,然后将结果展平成单个列表。

好吧,现在我们已经放弃了monads,让我们回顾一下这个难题...让我们假设你的初始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]

我将故意实现的细节留在这里,因为我不想完全毁掉你的难题,但我希望我已经在这里阐明了关于列表没有特别“特殊”的一点,或列表的列表,或其他。 我们所做的只是用类型为Sequence -> [Sequence]的新函数替换了KnightPos -> [KnightPos]类型的KnightPos -> [KnightPos] Sequence -> [Sequence] - 形状几乎相同。 因此,使用任何风格感觉自然的方式来填充每个函数的实现,然后一旦你有它的工作,回到一元风格,用>>=替换concatMap等等。


我建议让KnightPos成为一个数据结构,它不仅能够保存当前药水,还能够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个一元语句中从startend所有可能路径。

扰流警报

pathIn3 :: KnightPos - > KnightPos - > [[KnightPos]]
pathIn3 start end = do
p < - in3开始
警卫(p ==结束)
返回$ getHistory p


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

“......然后这会击败monad的全部点吧?” 那么,如果你想要所有的移动顺序,你就不要在每一步都使用>>=隐含的join 。 您可以定义一个函数,从给定起点返回长度为n的所有路径的列表,我们可以将这些路径作为传递的位置列表实现,以效率相反的顺序。

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

首先一个动作(或零)

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

然后进行任意数量的移动,此时您可以再次使用>>=将直接递归产生的类似特里结构加入到移动列表列表中:

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

可能有更好的方法来做到这一点,但它不会真正“击败”monad的全部点。

链接地址: http://www.djcxy.com/p/43355.html

上一篇: Haskell Monad Functions

下一篇: Haskell Monad bind operator confusion