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?
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 Maybes. 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With