Reputation:
Consider the following code:
run = runExcept $ do
case Just 1 of
Nothing -> throwE "escape 1"
Just x -> do
case Just 2 of
Nothing -> throwE "escape 2"
Just y -> do
case Just 3 of
Nothing -> throwE "escape 3"
Just z -> return z
Pretend the Just 1
, Just 2
, Just 3
are function calls that return Maybe Int
.
This whole function is inside an ExceptT
because I want to throw exceptions. But inside is really just a lot of Maybe
values being manipulated.
So, is it possible for me to leverage the behaviour of a Maybe
monad to avoid the stair-casing while still being able to throw exceptions?
Upvotes: 3
Views: 129
Reputation: 44634
It seems that you want to enrich a sequence of Maybe
actions with information pertaining to which one failed, if any. Why not implement enriching as a simple function?
enrich :: MonadError e m => e -> Maybe a -> m a
enrich e Nothing = throwError e
enrich e (Just x) = return x
I'm using MonadError
- the class of monads m
which can throw errors of type e
- to generalise the type of enrich
. For example, we can use it as if it had the type e -> Maybe a -> Except e a
or e -> Maybe a -> Either e a
, because Except
and Either
are both instances of MonadError
.
Now you just need to use enrich
to lift your Maybe
values into the richer monadic context one at a time.
action = do
x <- enrich "escape 1" maybe1 -- look mum, no staircasing!
y <- enrich "escape 2" maybe2
z <- enrich "escape 3" maybe3
return [x, y, z]
If you're using your monad applicatively - that is, you're not using earlier results to determine later computations - there's an idiomatic way to generalise this function to work over an arbitrary number of Maybe
s. We're going to shove the Maybes
into a list, along with the extra data we need to enrich it - in this case, the error message. Then we can traverse
(née mapM
) the list to enrich each Maybe
inside it and join them up into a bigger monadic action.
enrichMaybes :: (Traversable t, MonadError e m) => t (e, Maybe a) -> m (t a)
enrichMaybes = traverse (uncurry enrich)
action = enrichMaybes [("escape 1", maybe1), ("escape 2", maybe2), ("escape 3", maybe3)]
The ability to treat effects as first-class citizens like this is why functional programming is a Good Thing.
Upvotes: 6