【问题标题】:How to map (Either String (a -> b)) to (Either String [(a -> b)])如何将 (Either String (a -> b)) 映射到 (Either String [(a -> b)])
【发布时间】:2015-12-08 11:02:29
【问题描述】:

我尝试通过自己的练习找到解决方案,并满足以下要求:

  • 我们需要根据给定的顺序移动对象。
  • 序列由动作组成。

以下是可能的动作: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 时失败。我不知道如何实现这个功能。

你有什么想法吗?

【问题讨论】:

  • 我本来打算推荐 Hoogle,但我发现让 Hoogle 给我正确答案非常困难。我从(Char -> Either String (a -> a)) -> (String -> Either String [(a -> a)]) 开始,但这给出了结果;获得正确答案需要我可以用更多多态的a 替换a -> a,以及我可以用更多多态的f 替换Either String。直到那时它才建议mapM。哦,好吧,至少我不必知道CharString 可以替换为更多态的b[b]
  • id 不是 a -> a 类型的唯一有效函数吗? (忽略错误、未定义、不安全的副作用等)

标签: haskell monads either


【解决方案1】:

这看起来像

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 建议的那样)改为使用更通用的 traverse。在 GHC 的最新版本中,traverse 在 Prelude 中;在旧版本中,您可能必须从 Data.Traversable 导入它才能使用它。

【讨论】:

  • 确实,parseAction其实是在割草机上工作的:),但是为了解释,我没有解释。
【解决方案2】:

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

你需要traverse:

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

在您的情况下,t[],因此,t CharStringtraverse parseAction 将穿过String,为每个Char 生成一个动作并收集结果。 traverse 使用EitherApplicative 实例来处理Lefts 和Rights,在第一个Left 处停止。

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

【讨论】:

  • mapM 也可以处理 Left 吗?
  • @MaximeB。是的,它确实。在实践中,mapMtraverse 除了类型签名之外没有区别。 (可以说,mapM 之所以存在,是因为在引入它的时候,ApplicativeTraversable 还不存在。它的工作原理是一样的,因为您可以将每个 Monad 设为 Applicative。如今,这反映在ApplicativeMonad 的超类。)
【解决方案3】:

如果你将 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

【讨论】:

  • 但是 amalloy 的解决方案更简洁。
  • 将“不可能”的错误隐藏在 headtail 后面会更糟。
猜你喜欢
  • 2011-11-06
  • 2012-10-18
  • 1970-01-01
  • 1970-01-01
  • 2019-10-23
  • 1970-01-01
  • 2016-10-27
  • 2021-11-10
  • 1970-01-01
相关资源
最近更新 更多