我试图找到一个自己锻炼的解决方案,具有以下要求:
以下是可能的行动:F,L,R
然后用一个字符串表示一个序列,如下所示:
"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时失败。我坚持不知道如何实现这个功能。
你有什么想法吗?
答案 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 Char
为String
。 traverse parseAction
将遍历String
,为每个Char
生成一个操作并收集结果。 traverse
使用Applicative
Either
实例处理Left
和Right
s,停在第一个Left
。
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