loading...

The Simplest MonadFail Instance

kakkun61 profile image Kazuki Okamoto Updated on ・2 min read

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

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

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

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)

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 

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

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 😉

Posted on by:

kakkun61 profile

Kazuki Okamoto

@kakkun61

岡本和樹 [o̞͑kä̝mo̞͑to̞͑ kä̝zʊ̠kɪ̟]

Discussion

markdown guide