混合Either和Maybe Monads

时间:2018-04-13 12:22:53

标签: haskell monads

我想我理解如何级联同一类型的Monad。我想将两个Monad组合在一起,根据它们执行操作:

我认为下面的代码恢复了问题:假设我们有一个函数来验证String包含" Jo"并附加" Bob"如果是这样的话,另一个验证字符串长度是> 8

hello函数将应用第一个,然后第二个应用于第一个结果并返回" Hello"对于所有那些在成功的情况下或者没有什么' (如果发生错误,我不知道这是什么,没有什么,不管是左边还是什么都没有。

我相信它是Monad变压器所需要的,但是我找不到一个可以帮助我开始的简洁例子。

我确切地认为这不是理论上的,因为Haskell包与Aither和其他与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 

3 个答案:

答案 0 :(得分:6)

使用函数将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

答案 1 :(得分:5)

你想要的是(在分类意义上)从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 " ++)

答案 2 :(得分:4)

除了李尧瑶给出的实际答案外,还有其他选择。这是两个。

也许 - 同构

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在失败时返回错误文本,因此将其返回值转换为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 

这与Li-yao Xia的答案基本相同 - 虽然不太实际,但也不那么特别。

first函数映射Either值的第一个(最左侧)个案。在这种情况下,如果validateContainsJoAndAppendBob的返回值为Left值,则它始终为Left (),因此您可以使用const忽略()输入并返回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")

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