DEV Community

Kazuki Okamoto
Kazuki Okamoto

Posted on • Edited on

The Simplest MonadFail Instance

The Japanese version is at はてなブログ.


Introduction

After fail was removed from Monad a long time ago, I prefer to type computations that may fail to MonadFail.

foo :: MonadFail m => m a
Enter fullscreen mode Exit fullscreen mode

This allows you to instantiate m to IO within the IO context, and to instantiate it to Maybe within the pure context for example.

-- within IO context
foo :: IO a

-- within pure context
foo :: Maybe a
Enter fullscreen mode Exit fullscreen mode

It is not happy that Maybe discards the failure messages. So should we use Either? Either is not actually a MonadFail instance. There was a proposal about this which was rejected because of the following reason.

the instances "get in the way of a user who wants to treat the parameter uniformly"

That being so I wanted the following Result type as the simplest MonadFail instance.

newtype Result a = Result (Either String a)

instance MonadFail Result where
  fail = Left
Enter fullscreen mode Exit fullscreen mode

There is actually an equivalent of this but it is deprecated. It is ErrorT of the mtl package.

either-result package

I released the either-result package which contains Result and some functions.

https://hackage.haskell.org/package/either-result

Actually Result is an alias of ResultT Identity and ResultT is a newtype of ExceptT of the transformers package.

type Result a = ResultT Identity a

newtype ResultT m a = ResultT (ExceptT String m a)
Enter fullscreen mode Exit fullscreen mode

What is the difference between ResultT and ExceptT is about MonadFail instances. On ResultT fail wraps a message with Left, while on ExceptT it calls fail of its base monad. Because of it, on ResultT its base monad is requested to be just Monad, but on ExceptT its one is requested to be MonadFail.

instance Monad m => MonadFail (ResultT m) where 

instance MonadFail m => MonadFail (ExceptT e m) where 
Enter fullscreen mode Exit fullscreen mode

You can use throwError and catchError because ResultT is also MonadError instance of the mtl package.

What about the exceptions package?

There is MonadThrow type class, isn't it? Yes, the exceptions package has MonadThrow and MonadCatch type classes. These requests something thrown and caught to be Exception instances. In my opinion, if you want to recognise thrown and caught things by their type, you should use them. And if you want just messages, use MonadFail.

class Monad m => MonadThrow m where
  throwM :: Exception e => e -> m a

class MonadThrow m => MonadCatch m where
  catch :: Exception e => m a -> (e -> m a) -> m a

class Monad m => MonadFail m where
  fail :: String -> m a
Enter fullscreen mode Exit fullscreen mode

Conclusion

  • on defining, type computations which may fail to MonadFail m => m a
  • on using, use it as IO a within IO context for example
  • on using, use it as Result a within a pure context
  • add star to the GitHub repository 😉

Top comments (0)