Clean Alternatives with MaybeT

Haskell’s abstraction facilities are awesome. Functor , Applicative , and Monad are all great, and Maybe is a pretty fantastic example of each. Lifting functions over optional values, combining optional values, and sequencing the possibility of Nothing ness are pretty powerful tools for cleaning up code. The first time I refactored some Maybe infested code like:

someFunc :: Int -> Maybe String someFunc i = case foo i of Nothing -> Nothing Just a -> case bar a of Nothing -> Nothing Just b -> Just ( show b )

into the elegant:

someFunc i = do a <- foo i b <- bar a pure ( show b )

I knew I was totally hooked.

The Monad instance for Maybe covers a common case: given some sequence of functions which may fail, we want to try them all and if any of them fail then we’ll short circuit it all. However, that’s not the only case. Very often, you’ll want to take the first thing that succeeds, rather than failing unless everything works. Something like this:

someOtherFunc :: Int -> Maybe String someOtherFunc i = do case foo i of Just a -> Just a Nothing -> case bar i of Just b -> Just b Nothing -> wat i

One of Haskell’s lesser known type classes is Alternative , which is precisely the abstraction we want here!

Alternative

class Applicative f => Alternative f where empty :: f a ( <|> ) :: f a -> f a -> f a

The Alternative class gives us empty , which is an “empty” value, and <|> , which allows us to define a way to choose between two values. The documentation tells us that empty should be an identity for <|> , and that <|> is a binary associative operator (huh, sounds like a monoid, right?)

Maybe has a nice Alternative instance that looks like this:

instance Alternative Maybe where empty = Nothing Just a <|> _ = Just a Nothing <|> b = b

Does this make sense? Well, if we have some Just 10 , and we choose between Nothing <|> Just 10 , then we’ll pick Just 10 . Likewise, if we choose between Just 10 <|> Nothing , we’ll take Just 10 . It’s associative, so we don’t need parentheses. a <|> b <|> c <|> d will choose the first value that isn’t empty .

Okay, so how can we rewrite someOtherFunc like this?

someOtherFunc :: Int -> Maybe String someOtherFunc i = foo i <|> bar i <|> wat i

Now that looks pretty nice! Definitely a lot cleaner than the previous one.

Transformers In Disguise

Raise your hand if you’ve written some Haskell code like this:

getFromCache :: String -> IO ( Maybe Record ) getFromDatabase :: String -> IO ( Maybe Record ) getFromRemoteAPI :: String -> IO ( Maybe Record ) retrieveRecord :: String -> IO ( Maybe Record ) retrieveRecord name = do mrec <- getFromCache name case mrec of Just rec -> pure ( Just rec ) Nothing -> do mrec' <- getFromDatabase name case mrec' of Just rec -> pure ( Just rec ) Nothing -> getFromRemoteAPI name

GROSS! That’s just as bad as before. Wouldn’t it be great if we could get that nice Maybe Alternative action going here?

Well, we can! The entire magic of a monad transformer is that we can enhance a base monad with features of another monad. Let’s cover the implementation of MaybeT and see how to use it to wrap our actions and get that choice.

newtype MaybeT m a = MaybeT { runMaybeT :: m ( Maybe a ) }

I’m going to elide the Functor and Applicative definitions – let’s get right into Monad :

instance Monad m => Monad ( MaybeT m ) where return = MaybeT . return . Just MaybeT ma >>= f = ???

??? has the type MaybeT m b , ma :: m (Maybe a) , and f :: a -> MaybeT m b . We need to get the a out of that ma value, but it’s a Monad , so we can only bind out of it. So we’ll have to start with the MaybeT constructor.

instance Monad m => Monad ( MaybeT m ) where return = MaybeT . return . Just MaybeT ma >>= f = MaybeT ???

The ??? value has the type m (Maybe b) now, which means that it’s in the same monad. This means we can use do and bind out of that original ma value!

instance Monad m => Monad ( MaybeT m ) where return = MaybeT . return . Just MaybeT ma >>= f = MaybeT $ do maybeA <- ma ???

We’ve got a maybeA :: Maybe a value now, so we’re not out of the weeds yet. We’ll case match on the value. If it’s Nothing , we’ll return Nothing since we can’t do anything else. Otherwise, we can continue!

instance Monad m => Monad ( MaybeT m ) where return = MaybeT . return . Just MaybeT ma >>= f = MaybeT $ do maybeA <- ma case maybeA of Nothing -> return Nothing Just a -> ???

Now that we’ve finally got that a , we need to apply it to f .

instance Monad m => Monad ( MaybeT m ) where return = MaybeT . return . Just MaybeT ma >>= f = MaybeT $ do maybeA <- ma case maybeA of Nothing -> return Nothing Just a -> f a

However, this isn’t quite right, because f a :: MaybeT m b , and we need m (Maybe b) ! We’ll unwrap with runMaybeT and it’ll work.

instance Monad m => Monad ( MaybeT m ) where return = MaybeT . return . Just MaybeT ma >>= f = MaybeT $ do maybeA <- ma case maybeA of Nothing -> return Nothing Just a -> runMaybeT ( f a )

Cool! Now, what’s the alternative instance look like?

instance Monad m => Alternative ( MaybeT m a ) where empty = MaybeT ( return Nothing ) MaybeT first <|> MaybeT second = ???

Well, we’ll want to check the first value, and if it’s Nothing , then we’ll check the second value.

instance Monad m => Alternative ( MaybeT m a ) where empty = MaybeT ( return Nothing ) MaybeT first <|> MaybeT second = MaybeT $ do maybeA <- first case maybeA of Just a -> return ( Just a ) Nothing -> ???

Well, now we’ve taken care of the first action. If it was Nothing , then we’ll need to bind out of the second action.

instance Monad m => Alternative ( MaybeT m a ) where empty = MaybeT ( return Nothing ) MaybeT first <|> MaybeT second = MaybeT $ do maybeA <- first case maybeA of Just a -> return ( Just a ) Nothing -> do maybeA' <- second case maybeA' of Just a -> return ( Just a ) Nothing -> return Nothing

There’s some redundancy here that we can clean up:

instance Monad m => Alternative ( MaybeT m a ) where empty = MaybeT ( return Nothing ) MaybeT first <|> MaybeT second = MaybeT $ do maybeA <- first case maybeA of Just a -> return ( Just a ) Nothing -> second

Cool!

Using the Alternative

Now that we’ve got our Alternative , we can use it with our previous functions:

getFromCache :: String -> IO ( Maybe Record ) getFromDatabase :: String -> IO ( Maybe Record ) getFromRemoteAPI :: String -> IO ( Maybe Record ) retrieveRecord :: String -> IO ( Maybe Record ) retrieveRecord name = runMaybeT $ MaybeT ( getFromCache name ) <|> MaybeT ( getFromDatabase name ) <|> MaybeT ( getFromRemoteAPI name )