混合 Either 和 Maybe Monad

Mixing Either and Maybe Monads

我想我明白如何级联相同类型的Monad。我想将两个 Monad 组合在一起以执行基于它们的操作:

我认为下面的代码恢复了这个问题:假设我们有一个函数验证一个字符串是否包含 "Jo" 并在是这种情况下附加 "Bob" 到它,另一个验证它字符串长度 > 8

hello 函数将应用第一个,然后在第一个和 return "Hello" 的结果上应用第二个,以防成功或 'Nothing'(我不不知道这是什么 'Nothing' 顺便说一句,Left or Nothing)以防出错。

我相信它是我需要的 Monad 转换器,但我找不到一个简明的例子来帮助我开始。

我明确指出这不是理论上的,因为周围有 Haskell 包可以与 Either 一起使用,而其他包可以与 Maybe

一起使用
validateContainsJoAndAppendBob :: String -> Maybe String
validateContainsJoAndAppendBob l =
  case isInfixOf "Jo" l of
    False -> Nothing
    True  -> Just $ l ++ "Bob"

validateLengthFunction :: Foldable t => t a -> Either String (t a)
validateLengthFunction l =
  case (length l > 8)  of
    False -> Left "to short"
    True  -> Right l

-- hello l = do
  -- v <- validateContainsJoAndAppendBob l
  -- r <- validateLengthFunction v
  -- return $ "Hello " ++ r 

使用函数将Maybe转换为Either

note :: Maybe a -> e -> Either e a
note Nothing e = Left e
note (Just a) _ = Right a

hello l = do
  v <- validateContainsJoAndAppendBob l `note` "Does not contain \"Jo\""
  r <- validateLengthFunction v
  return $ "Hello " ++ r

除了夏丽瑶给出的实用答案外,还有其他选择。这里有两个。

Maybe-Either 同构

Maybe aEither () a同构,这意味着两者之间存在无损转换:

eitherFromMaybe :: Maybe a -> Either () a
eitherFromMaybe (Just x) = Right x
eitherFromMaybe  Nothing = Left ()

maybeFromEither :: Either () a -> Maybe a
maybeFromEither (Right x) = Just x
maybeFromEither (Left ()) = Nothing

您可以使用其中一个翻译成另一个。由于 validateLengthFunction return 是失败时的错误文本,将其 return 值转换为 Maybe String 值将是一种有损翻译,因此最好使用 eitherFromMaybe.

但是,问题是这只会给你一个 Either () String 值,而你需要一个 Either String String。您可以通过利用 Either 作为 Bifunctor 实例来解决这个问题。首先,

import Data.Bifunctor

然后你可以把hello写成:

hello :: String -> Either String String
hello l = do
  v <-
    first (const "Doesn't contain 'Jo'.") $
    eitherFromMaybe $
    validateContainsJoAndAppendBob l
  r <- validateLengthFunction v
  return $ "Hello " ++ r 

这基本上与夏丽瑶的回答相同 - 实用性差一点,但临时性也差一点。

first 函数映射 Either 值的第一个(最左侧)案例。在这种情况下,如果 validateContainsJoAndAppendBob 的 return 值是 Left 值,它总是会是 Left (),所以你可以使用 const 来忽略() 输入和 return 一个 String 值。

完成任务:

*Q49816908> hello "Job, "
Left "to short"
*Q49816908> hello "Cool job, "
Left "Doesn't contain 'Jo'."
*Q49816908> hello "Cool Job, "
Right "Hello Cool Job, Bob"

与下一个相比,我更喜欢这个替代方案,但只是为了完整起见:

Monad 变形金刚

另一种选择是使用 Monad 转换器。您可以将 Maybe 包装在 EitherT 中,或者反过来将 Either 包装在 MaybeT 中。下面的例子就是后者。

import Control.Monad.Trans (lift)
import Control.Monad.Trans.Maybe (MaybeT(..))

helloT :: String -> MaybeT (Either String) String
helloT l = do
  v <- MaybeT $ return $ validateContainsJoAndAppendBob l
  r <- lift $ validateLengthFunction v
  return $ "Hello " ++ r

这样也行,不过这里还是要处理JustNothingLeftRight的各种组合:

*Q49816908> helloT "Job, "
MaybeT (Left "to short")
*Q49816908> helloT "Cool job, "
MaybeT (Right Nothing)
*Q49816908> helloT "Cool Job, "
MaybeT (Right (Just "Hello Cool Job, Bob"))

如果你想剥掉MaybeT包装纸,你可以使用runMaybeT:

*Q49816908> runMaybeT $ helloT "Cool Job, "
Right (Just "Hello Cool Job, Bob")

在大多数情况下,我可能会选择第一个选项...

您想要的是(在分类意义上)从 MaybeEither String 的自然转换,maybe 函数可以提供。

maybeToEither :: e -> Maybe a -> Either e a
maybeToEither e = maybe (Left e) Right


hello l = do
  v <- maybeToEither "No Jo" (validateContainsJoAndAppendBob l)
  r <- validateLengthFunction v
  return $ "Hello " ++ r

您可以使用 Control.Monad 中的 <=< 来组合两个验证器。

hello l = do
    r <- validateLengthFunction <=< maybeToEither "No Jo" . validateContainsJoAndAppendBob $ l
    return $ "Hello " ++ r

您还可以使用 >=>return 将整个事情变成一个单一的可怕的无点定义。

hello =      maybeToEither "No Jo" . validateContainsJoAndAppendBob
         >=> validateLengthFunction
         >=> return . ("Hello " ++)