混合 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 a
与Either () 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
这样也行,不过这里还是要处理Just
、Nothing
、Left
、Right
的各种组合:
*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")
在大多数情况下,我可能会选择第一个选项...
您想要的是(在分类意义上)从 Maybe
到 Either 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 " ++)
我想我明白如何级联相同类型的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 a
与Either () 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
这样也行,不过这里还是要处理Just
、Nothing
、Left
、Right
的各种组合:
*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")
在大多数情况下,我可能会选择第一个选项...
您想要的是(在分类意义上)从 Maybe
到 Either 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 " ++)