FailT
A 'FailT' monad transformer that plays well with 'MonadFail'
https://github.com/lehins/FailT
| LTS Haskell 24.16: | 0.1.2.0 |
| Stackage Nightly 2025-10-24: | 0.1.2.0 |
| Latest on Hackage: | 0.1.2.0 |
FailT-0.1.2.0@sha256:8b0f55600f709f909d06204833f30c7b9316cdecb8e863404252d6dbdac7a56c,2594Module documentation for 0.1.2.0
- Control
- Control.Monad
- Control.Monad.Trans
- Control.Monad
FailT
This package solves a fairly simple, but very common problem of gracefully converting a
monadic computation that uses MonadFail into either a result or a string failure
message(s).
| Github Actions | Coveralls | Hackage | Nightly | LTS |
|---|---|---|---|---|
Motivation
When we have a function that can fail in a MonadFail, there is no instance in base
that would allow us to get the failure message without a runtime exception. It is best to
demonstrate the problem with an example.
Here is
formatParseM
function from the time package, which is
designed to parse time:
formatParseM :: MonadFail m => Format t -> String -> m t
We can use it out of the box with various packages like aeson, attoparsec, binary,
etc. Here is how it could be used to successfully parse a string with time in the IO
monad:
λ> import Data.Time (UTCTime)
λ> import Data.Time.Format.ISO8601 (formatParseM, iso8601Format)
λ> formatParseM iso8601Format "2023-01-08T00:29:00Z" :: IO UTCTime
2023-01-08 00:29:00 UTC
However, when it comes to bad input, there is not a single monad in base or other
package that is wired with GHC that has a MonadFail instance, which would allow us to
gracefully fail and retrieve the error message. Below are all the instances from base:
λ> formatParseM iso8601Format "Bad time" :: IO UTCTime
*** Exception: user error (no parse of "Bad time")
λ> formatParseM iso8601Format "Bad time" :: Maybe UTCTime
Nothing
λ> formatParseM iso8601Format "Bad time" :: [UTCTime]
[]
Solution
This is where FailT package comes to help:
λ> import Control.Monad.Trans.Fail.String
λ> runFail $ formatParseM iso8601Format "Bad time" :: Either String UTCTime
Left "no parse of \"Bad time\""
λ> runFail $ formatParseM iso8601Format "2023-01-08T00:29:00Z" :: Either String UTCTime
Right 2023-01-08 00:29:00 UTC
Features
Monad transformer
Naturally, as the package name suggests, it provides a FailT monad transformer.
The example above used the Fail type synonym, whcih is restricts the underlying monad to
Identity. Below is the example of running FailT with IO:
λ> import Control.Monad.IO.Class
λ> runFailT (liftIO . print . utctDayTime =<< formatParseM iso8601Format "2023-01-08T00:29:00Z")
1740s
Right ()
λ> runFailT (liftIO . print . utctDayTime =<< formatParseM iso8601Format "Bad input")
Left "no parse of \"Bad input\""
Polymorphic failure
Thus far examples only showed using String type for failure messages, but that is not a
requirement. This library was designed to be agnostic with respect to failure message type
with restriction to IsString type class. Reason for the constraint is because the
failure message normally originates with the fail function and is string like by its
nature:
fail :: MonadFail m => String -> m ()
The more general implementation is located in import Control.Monad.Trans.Fail, which
contains polymorphic implementation that allows the user to choose a more specific type
for the failure type e:
runFailT :: (IsString e, Semigroup e, Functor m) => FailT e m a -> m (Either e a)
This package implements convenience modules:
Control.Monad.Trans.Fail.StringControl.Monad.Trans.Fail.Text
which provide type synonyms and functions with more restricted failure types: String and
Text respectfully. Modules were designed to be drop-in replacements of each other.
Convenient instances
There are many type class instances for FailT monad. Such as instances for type classes
from mtl package, MonadIO instance, etc. Most of them rely on the instances of the
underlying monad by lifting the functionality. However, here are some of the more notable
and useful instances that do not require corresponding instances from the underlying
monad:
-
MonadFailwithMonadic sequencing, which allow to stop the computation upon the first invocation offail.λ> runFailT (fail "Failure!?" >> pure "Success!!") Left "Failure!?" -
Alternative, which will continue until the first successful computation is encountered.λ> runFailT (fail "Failure!?" <|> pure "Success!!") Right "Success!!" -
SemigroupandMonoid, which will not stop until all of the actions are executed. Produced at the end are either all of the failures or the results of all of the successful cases combined with<>λ> runFailT (pure ["Success!!"] <> fail "Failure!?" <> pure ["At", "Last!"]) Right ["Success!!","At","Last!"] λ> runFailT mempty :: IO (Either String ()) Left "No failure reason given"
Changes
Changelog for FailT
0.1.2.0
- Compatiblity with mtl-2.3
- Add
failManyT
0.1.1.0
- Remove unnecessary
IsStringconstraint from instance definitions for GHC >= 8.10 - Add
MonadFixinstance. - Add
MonadRWSinstance. - Add
MonadAccumandMonadSelectinstances, whenever mtl-2.3 or newer is used. - Add
MonadThrowinstance. - Fix a bug in
Applicativeinstance. Implementation for*>did not short circuit mondic computation. - Add
throwErrorFailTthat usesMonadErrorinterface.
0.1.0.0
- Initial release