Haskell添加编写器功能

时间:2010-12-26 23:39:12

标签: haskell monads

这里是用于计算骑士是否可以移动到x移动中所需位置的片段:

import Control.Monad (guard)
import Control.Monad.Writer    

type KnightPos = (Int,Int)
-- function returning array of all possible kinght moves from desired position
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')

-- nice little function tells us
-- whether knight can move to desired position within x moves
reaches :: KnightPos -> KnightPos -> Int -> Bool
reaches _ _ 0 = False
reaches from pos n =
    any (\p -> p == pos || reaches p pos (n-1)) $ moveKnight from


-- the result is True or False
-- does knight can move from cell 6,2 to cell 6,3 within 3 moves
main = print $ reachesm (6,2) (6,1) 3

现在我想将Writer monad添加到'reach'功能,但完全丢失了 我来的是,

-- not so nice and little yet
reachesm :: KnightPos -> KnightPos -> Int -> Writer [String] [Bool]
reachesm _ _ 0 = return [False]
reachesm from pos n = do
    tell [ "-->" ++ (show pos) ]
    p <- moveKnight from -- ???
    np <- reachesm p pos (n-1)
    return(p == pos || any np)

但它甚至没有编译。我怀疑它的时间是monad transormers吗?

UPD: 所以,最后我们来重写,但我仍然不满意,因为 reachm与纯变体的运行方式不同,它可以深度计算所有 n 步骤,但是 我希望它一旦找到答案就停止迭代。 这样修改难吗? 另一个问题是关于懒惰,似乎在块计算不是懒惰是真的吗?

reachesm :: KnightPos -> KnightPos -> Int -> Writer [String] Bool
reachesm _    _   0 = return False
reachesm from pos n = do
   tell [ "-->" ++ (show from) ]
   let moves = moveKnight from
   np <- forM moves (\p -> reachesm p pos (n-1))
   return (any (pos ==) moves || or np)

5 个答案:

答案 0 :(得分:4)

好的,我们的目标是将此功能放入Wrtier monad。

reaches :: KnightPos -> KnightPos -> Int -> Bool
reaches _ _ 0 = False
reaches from pos n =
    any (\p -> p == pos || reaches p pos (n-1)) $ moveKnight from

所以,让我们从类型签名开始。只需在结果类型周围添加Writer

reaches :: KnightPos -> KnightPos -> Int -> Writer [String] Bool

原始函数未返回[Bool],因此新函数没有理由返回Writer [String] [Bool]。提升基础案例的返回值:

reaches _ _ 0 = return False

正如您所怀疑的那样,处理递归案例会变得有点棘手。让我们开始时就像tell当前pos所做的那样,你做得对。

reaches from pos n = do
    tell ["-->" ++ show pos]

moveKnight不在编写器monad中,因此我们不必使用<-来绑定它。只需使用let(即如果我们需要,我们可以在使用新变量时替换moveKnight pos):

    let moves = moveKnight from

现在让我们获取递归结果列表。这次我们必须绑定,因为我们从Bool中获取Writer [String] Bool。我们将使用mapmapM :: (a -> m b) -> [a] -> m [b]

的monadic变体
    np <- mapM (\p -> reachesm p pos (n-1)) ps

现在np :: [Bool]。那么我们就完成了你的逻辑:

    return (any (pos ==) moves || or np)

or :: [Bool] -> Bool只是any id

请记住,要绑定变量,当您想从a获取m a时,请使用<-,否则请使用let

要从main使用它,您可以使用runWriter :: Writer w a -> (w,a)

main = print $ runWriter (reachesm (6,2) (6,1) 3)

此代码仍然有错误,但它会编译并产生您通过编写器通道告诉它的内容,因此您应该可以轻松调试剩余的问题。希望这有帮助。

答案 1 :(得分:4)

听起来你真的致力于使用编写器monad。所以这是一个解决方案:

reachesm :: KnightPos -> KnightPos -> Int -> [Writer [String] Bool]
reachesm from pos n | from == pos = return (return True)
reachesm _ _ 0 = return (return False)
reachesm from pos n = do
    p <- moveKnight from
    map (tell [show from ++ "-->" ++ show p] >>) $ reachesm p pos (n-1)

main = print . filter fst . map runWriter $ reachesm (6,2) (6,3) 3

但这很愚蠢。编写器monad仅用作列表的巴洛克式界面。 Writer不是你问题的解决方案,尽管你明确希望它有多少。以下是我编写此算法的方法:

-- returns all paths of length at most n to get to target
paths :: Int -> KnightPos -> KnightPos -> [[KnightPos]]
paths 0 _ _ = []
paths n target p 
    | p == target = return [p]
    | otherwise   = map (p:) . paths (n-1) target =<< moveKnight p

main = print $ paths 4 (6,3) (6,2) 

没有作家monad,只是友好的旧(:)运营商。

答案 2 :(得分:3)

这是一个有效的版本:

main = print $ runWriterT (reachesm (6,2) (6,5) 4)

reachesm :: KnightPos -> KnightPos -> Int -> WriterT [String] [] Bool
reachesm _ _ (-1) = return False
reachesm from pos n 
  | from == pos = tell [ "-->" ++ (show from) ] >> return True
  | otherwise   = 
   do
     p <- lift (moveKnight from) 
     t <- reachesm p pos (n-1)
     guard t 
     tell [ "-->" ++ (show from) ]
     return True

您的moveKnight功能也可以这样写:

moveKnight :: KnightPos -> [KnightPos]
moveKnight (c,r) = filter legal possible
       where possible = [ (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])

答案 3 :(得分:2)

对于在树中寻找路径而言,(至少对我来说)更容易一点。

首先我们从Data.Tree导入一些函数:

import Data.Tree (levels, unfoldTree)

现在我们编写一个用于展开具有历史记录的树的函数,获取树的顶部n + 1级别,并查看它们是否包含所需的位置:

reaches :: KnightPos -> KnightPos -> Int -> Maybe [KnightPos]
reaches from pos n = lookup pos . concat . take (n + 1) $ levels tree
  where
    tree = unfoldTree unfolder (from, [])
    unfolder (p, hist) = ((p, hist'), map (flip (,) hist') $ moveKnight p)
      where hist' = p : hist

这给了我们在给定步数中从结束位置到开头的路径(如果存在):

*Main> reaches (6,2) (6,1) 3
Just [(6,1),(7,3),(8,1),(6,2)]

(如果我们想要一条从头到尾的路径,我们当然可以改变它。)

这是一个快速的解决方案,它不一定非常有效,但我发现它在概念上很简单。

答案 4 :(得分:0)

这是我最近的尝试:

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') 


findpath :: KnightPos -> KnightPos -> Int -> [[KnightPos]]
findpath start end steps = trail [start] steps
   where trail curtrail steps = do
               nextstep <- moveKnight $ last curtrail
               if steps == 1 then
                  do guard (nextstep == end)
                     return (curtrail ++ [nextstep])
                else trail (curtrail ++ [nextstep]) (steps - 1)