In addition to the practical answer given by Li-yao Xia, there's other alternatives. Here's two.
Maybe-Either isomorphism
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:
Monad transformers
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...