如何映射(字符串(a - > b))到(字符串[(a - > b)])

时间:2015-09-11 19:40:38

标签: haskell monads either

我试图找到一个自己锻炼的解决方案,具有以下要求:

  • 我们需要针对给定的序列移动一个对象。
  • 序列由动作组成。

以下是可能的行动:F,L,R

  • F:前进
  • L:向左旋转90°
  • R:向右旋转90°

然后用一个字符串表示一个序列,如下所示:

"FFLRLFF"

我想解析上面的序列(并处理错误),然后将每个动作绑定到一个函数,如下所示:

parseAction :: Char -> Either String (a -> a)
parseAction 'F' = Right moveForward
parseAction 'L' = Right turnLeft
parseAction 'R' = Right turnRight
parseAction s   = Left ("Unkown action : " ++ [s])

-- Implementation omitted
moveForward :: a -> a
turnLeft :: a -> a
turnRight :: a -> a

现在我想要的是具有以下签名的东西:

parseSequence :: String -> Either String [(a -> a)]

我希望使用多次parseAction函数来解析完整序列,并在返回Left时失败。我坚持不知道如何实现这个功能。

你有什么想法吗?

3 个答案:

答案 0 :(得分:11)

这看起来像

mapM :: Monad m => (a -> m b) -> [a] -> m [b]

其中

a ~ Char
b ~ (a -> a)
m ~ (Either String)

所以一个实现就是:

parseSequence :: String -> Either String [a -> a]
parseSequence = mapM parseAction

顺便说一句,请注意,您的parseAction并不真的想要使用(a -> a)类型,这类型必须适用于所选的任何类型a由呼叫该功能的人。您希望它使用类型(Location -> Location),其中Location是您用来表示您正在移动的对象的位置的任何类型。

与mapM等效,你可以(如duplode所建议的)使用遍历,这稍微更通用。最近版本的GHC遍历是Prelude;在旧版本中,您可能必须从Data.Traversable导入它才能使用它。

答案 1 :(得分:7)

注意:为了更加清晰,我将使用Thing -> Thing而不是a -> a

您需要traverse

traverse parseAction
  :: Traversable t => t Char -> Either String (t (Thing -> Thing))

在您的情况下,t[],因此t CharStringtraverse parseAction将遍历String,为每个Char生成一个操作并收集结果。 traverse使用Applicative Either实例处理LeftRight s,停在第一个Left

在这种情况下,amalloy的答案中的P.S。:mapM等同于traverse。它们之间的唯一区别是traverse更为通用,因为它只需要您在遍历时使用的仿函数Applicative(而不是Monad)。

答案 2 :(得分:2)

如果您将parseAction映射到源字符串上,那么您将获得部分内容。在GHCi:

> :type map parseAction "FFRRF"
[Either String (a->a)]

所以现在你可以将它折叠成一个Either值

validActions = foldr f (Right [])
   where
      f (Left str) _ = Left str
      f (Right x) (Right xs) = Right (x:xs)
      f (Right x) (Left str) = error "Can't happen"

然而,这有一个恼人的“错误”案例。所以摆脱它:

import Data.Either

validActions vs = if null ls then Right rs else Left $ head ls
   where (ls, rs) = partitionEithers vs