The Simplest MonadFail Instance
Kazuki Okamoto
Posted on September 2, 2020
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
withinIO
context for example - on using, use it as
Result a
within a pure context - add star to the GitHub repository 😉
Posted on September 2, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 15, 2024