哈斯克尔:monad monad

时间:2015-08-17 12:18:21

标签: haskell monads

我正在学习一些Haskell,我对这些Monads有些麻烦,我了解它们并知道它们是什么,但在这种特殊情况下我有一些问题。在LYAH上学的时候,我遇到了一个练习,这个练习是关于计算你可以用骑士(来自国际象棋游戏)进行3次动作的位置,我们使用这样的列表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')

这是有效的,如果我给这个函数我的位置,它可以很好地计算未来可能的位置,但现在我正在寻找实现其中的Writer monad所以我可以检索我是如何达到这一点的。 所以我做了这个功能,

假设,

type KnightRoute = Writer [KnightPos] KnightPos

moveKnight' :: KnightPos -> [KnightRoute]
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 $ toKr (c',r') (c,r)
 where toKr pos oldpos = Writer (pos,[oldpos])

如果我给它一个KnightPos,但是使用monad我无法从KnightPos中提取KnightRoute来执行该函数...

*Main> let a = moveKnight' (2,4) !! 0
*Main> runWriter a
((4,3),[(2,4)])
*Main> a >>= moveKnight'

<interactive>:4:7:
Couldn't match type ‘[]’ with ‘Writer [KnightPos]’
Expected type: KnightPos -> Writer [KnightPos] KnightRoute
  Actual type: KnightPos -> [KnightRoute]
In the second argument of ‘(>>=)’, namely ‘moveKnight'’
In the expression: a >>= moveKnight'

我理解为什么它无法正常工作,我从我的作家中提取(4,3),然后我将其提交给KnightPos'。但是KnightPos'会返回KnightRoute的列表,我需要KnightRoute,这是逻辑错误,但我不知道该怎么做。使用Monads有一种简单的方法吗?

感谢提前:)

2 个答案:

答案 0 :(得分:6)

这种“两个monad的组合”在Haskell中是一个非常普遍的事情。幸运的是,语言足够灵活,我们可以很好地抽象出来。

从数学上讲,你想要的是composition of two functors。而不是新类型,这通常用变换器的概念表示:而不是直接使用Writer monad,而是使用WriterT monad变换器。 WriterT w [] a basically the same[Writer w a],因此在您的情况下,您可以使用:

import Control.Monad.Trans.Class
import Control.Monad.Trans.Writer

moveKnight'' :: KnightPos -> WriterT [] [KnightPos] KnightPos
moveKnight'' (c,r) = do
   (c',r') <- lift [(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])
   tell [(c,r)]
   return (c',r')

答案 1 :(得分:1)

你可以写

a' :: Int -> KnightRoute
a' i = a >>= \p -> moveKnight' p !! i

这里i用于消除作者的内部列表。而且,由于懒惰,您可以将Int -> a变为[a]

asList :: (Int -> a) -> [a]
asList f = map f [1..]

然后a'的所有路线列表都是

a's :: [KnightRoute]
a's = asList a'

把所有东西放在一起:

moveKnight :: KnightRoute -> [KnightRoute]
moveKnight k = map (\i -> k >>= \p -> moveKnight' p !! i) [1..]