Reputation: 2167
I think I understand how to cascade Monad of the same type. I would like to combine two Monads together to perform an operation based on them :
I think the code below resume the problem : suppose we have a function that validates that a String contains "Jo" and append "Bob" to it if it's the case, and another one that validates that the String length is > 8
The hello function would apply the first , then the second on the result of the first and return "Hello" to all that in case of success or 'Nothing' (I don't know what is this 'Nothing' btw , Left or Nothing) in case of error.
I believe that it's around Monad transformer what I need but I could not find a concise example that would help me to start.
I precise that this is nothing theoratical as there is around Haskell package that works with Either and others that works with 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
Upvotes: 4
Views: 2118
Reputation: 532303
What you want is (in the categorical sense) a natural transformation from Maybe
to Either String
, which the maybe
function can provide.
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
You can use <=<
from Control.Monad
to compose the two validators.
hello l = do
r <- validateLengthFunction <=< maybeToEither "No Jo" . validateContainsJoAndAppendBob $ l
return $ "Hello " ++ r
You can also use >=>
and return
to turn the whole thing into a single monstrous point-free definition.
hello = maybeToEither "No Jo" . validateContainsJoAndAppendBob
>=> validateLengthFunction
>=> return . ("Hello " ++)
Upvotes: 5
Reputation: 233407
In addition to the practical answer given by Li-yao Xia, there's other alternatives. Here's two.
Maybe a
is isomorphic to Either () a
, which means that there's a lossless translation between the two:
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
You can use one of these to translate to the other. Since validateLengthFunction
returns an error text on failure, it would be a lossy translation to turn its return value into a Maybe String
value, so it's better to use eitherFromMaybe
.
The problem with that, though, is that this will only give you an Either () String
value, and you need an Either String String
. You can solve this by taking advantage of Either
being a Bifunctor
instance. First,
import Data.Bifunctor
and then you can write hello
as:
hello :: String -> Either String String
hello l = do
v <-
first (const "Doesn't contain 'Jo'.") $
eitherFromMaybe $
validateContainsJoAndAppendBob l
r <- validateLengthFunction v
return $ "Hello " ++ r
This essentially does the same as Li-yao Xia's answer - a little less practical, but also a little less ad-hoc.
The first
function maps the first (left-most) case of an Either
value. In this case, if the return value from validateContainsJoAndAppendBob
is a Left
value, it's always going to be Left ()
, so you can use const
to ignore the ()
input and return a String
value.
This gets the job done:
*Q49816908> hello "Job, "
Left "to short"
*Q49816908> hello "Cool job, "
Left "Doesn't contain 'Jo'."
*Q49816908> hello "Cool Job, "
Right "Hello Cool Job, Bob"
This alternative I prefer to the next one, but just for completeness' sake:
Another option is using Monad transformers. You can either wrap the Maybe
in an EitherT
, or conversely wrap an Either
in MaybeT
. The following example does the latter.
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
This also works, but here you still have to deal with the various combinations of Just
, Nothing
, Left
, and 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"))
If you want to peel off the MaybeT
wrapper, you can use runMaybeT
:
*Q49816908> runMaybeT $ helloT "Cool Job, "
Right (Just "Hello Cool Job, Bob")
In most cases, I'd probably go with the first option...
Upvotes: 7
Reputation: 33569
Use a function to convert Maybe
to 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
Upvotes: 8