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个一元语句中从start到end所有可能路径。 
扰流警报
  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