hoist-error 0.2.1.0 → 0.3.1.0
raw patch · 4 files changed
Files
- changelog.md +16/−0
- hoist-error.cabal +15/−13
- src/Control/Monad/Error/Hoist.hs +113/−87
- src/Control/Monad/Fail/Hoist.hs +153/−0
changelog.md view
@@ -1,3 +1,19 @@+0.3.1.0++* Add `Control.Monad.Error.Hoist.hoistError'` and+ `Control.Monad.Error.Hoist.hoistErrorM'`++0.3.0.0++* Replaced the `HoistError` typeclass, which is about monads, with a+ simpler `PluckError` typeclass that is about extracting errors from+ values.+* Removed the instance for `Except e`, as it triggers overlapping+ instance errors when attempting to hoist `ExceptT e m` for unknown+ monads `m`.+* Introduced a parallel `Control.Monad.Fail.Hoist` module, for+ hoisting error messages into `MonadFail`.+ 0.2.1.0 * Removed unicode syntax and variables
hoist-error.cabal view
@@ -1,5 +1,5 @@ name: hoist-error-version: 0.2.1.0+version: 0.3.1.0 synopsis: Some convenience facilities for hoisting errors into a monad description: Provides a typeclass and useful combinators for hoisting errors into a monad. license: MIT@@ -10,27 +10,29 @@ category: Control build-type: Simple cabal-version: >=1.10-tested-with: GHC == 7.8.4, GHC == 7.10.3, GHC == 8.0.2, GHC == 8.2.2, GHC == 8.4.1 extra-source-files: changelog.md -tested-with: GHC == 7.8.4- , GHC == 7.10.3- , GHC == 8.0.2- , GHC == 8.2.2- , GHC == 8.4.1--+tested-with: GHC == 8.8.4+ , GHC == 8.10.7+ , GHC == 9.0.1+ , GHC == 9.2.7+ , GHC == 9.4.5+ , GHC == 9.6.4+ , GHC == 9.8.2+ , GHC == 9.10.2+ , GHC == 9.12.2 source-repository head type: git location: https://github.com/alephcloud/hs-hoist-error.git library- exposed-modules: Control.Monad.Error.Hoist- build-depends: base >=4.7 && <4.12- , mtl >=2.1 && <2.3- , either >=4 && <6+ exposed-modules:+ Control.Monad.Error.Hoist+ Control.Monad.Fail.Hoist+ build-depends: base >=4.13 && <4.22+ , mtl >=2.2 && <2.4 hs-source-dirs: src default-language: Haskell2010
src/Control/Monad/Error/Hoist.hs view
@@ -1,44 +1,52 @@-{-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE GADTs #-}-{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-} --- | 'HoistError' extends 'MonadError' with 'hoistError', which enables lifting--- of partiality types such as 'Maybe' and @'Either' e@ into the monad.+-- | This module provides helper functions for lifting partiality types into+-- error-carrying monads like 'ExceptT'. -- -- For example, consider the following @App@ monad that may throw @BadPacket@ -- errors: -- -- @--- data AppError = BadPacket 'String'+-- data AppError = BadPacket 'Text' -- -- newtype App a = App ('EitherT' AppError 'IO') a -- deriving ('Functor', 'Applicative', 'Monad', 'MonadError' AppError, 'MonadIO') -- @ ----- We may have an existing function that parses a 'String' into a @'Maybe' Packet@+-- We may have an existing function that attempts to parse a 'ByteString': -- -- @--- parsePacket :: 'String' -> 'Maybe' Packet+-- parsePacket :: 'ByteString' -> 'Either' 'Text' Packet -- @ ----- which can be lifted into the @App@ monad with 'hoistError'+-- We can lift this error into the @App@ monad using @('<%?>')@: -- -- @--- appParsePacket :: 'String' -> 'App' Packet--- appParsePacket s = 'hoistError' (\\() -> BadPacket "no parse") (parsePacket s)+-- appParsePacket :: 'ByteString' -> 'App' Packet+-- appParsePacket s = parsePacket s \<%?\> BadPacket -- @ ----- Similar instances exist for @'Either' e@ and @'EitherT' e m@.+-- Instances also exist for extracting errors from other partiality types+-- like @'Either' e@ and @'ExceptT' e m@. module Control.Monad.Error.Hoist- ( HoistError(..)+ ( hoistError+ , hoistError' , hoistErrorM+ , hoistErrorM'+ -- ** Operators+ -- $mnemonics , (<%?>) , (<%!?>) , (<?>) , (<!?>)+ -- * Helper class+ , PluckError(..) ) where import Control.Monad ((<=<))@@ -46,89 +54,81 @@ import Data.Either (Either, either) -#if MIN_VERSION_mtl(2,2,2) import Control.Monad.Except (Except, ExceptT, runExcept, runExceptT)-#else-import Control.Monad.Error (Error, ErrorT, runErrorT)-#endif -#if MIN_VERSION_either(5,0,0)--- Control.Monad.Trans.Either was removed from @either@ in version 5.-#else-import Control.Monad.Trans.Either (EitherT, eitherT, runEitherT)-#endif---- | A tricky class for easily hoisting errors out of partiality types (e.g.--- 'Maybe', @'Either' e@) into a monad. The parameter @e@ represents the error--- information carried by the partiality type @t@, and @e'@ represents the type--- of error expected in the monad @m@.+-- | Given a conversion from the error in @t a@ to @e'@, we can hoist the+-- computation into @m@. ---class Monad m => HoistError m t e e' | t -> e where-- -- | Given a conversion from the error in @t a@ to @e'@, we can hoist the- -- computation into @m@.- --- -- @- -- 'hoistError' :: 'MonadError' e m -> (() -> e) -> 'Maybe' a -> m a- -- 'hoistError' :: 'MonadError' e m -> (a -> e) -> 'Either' a b -> m b- -- 'hoistError' :: 'MonadError' e m -> (a -> e) -> 'ExceptT' a m b -> m b- -- @- hoistError- :: (e -> e')- -> t a- -> m a--instance MonadError e m => HoistError m Maybe () e where- hoistError f = maybe (throwError $ f ()) return--instance MonadError e' m => HoistError m (Either e) e e' where- hoistError f = either (throwError . f) return--#if MIN_VERSION_either(5,0,0)--- Control.Monad.Trans.Either was removed from @either@ in version 5.-#else-instance (m ~ n, MonadError e' m) => HoistError m (EitherT e n) e e' where- hoistError f = eitherT (throwError . f) return-#endif--#if MIN_VERSION_mtl(2,2,2)-instance MonadError e' m => HoistError m (Except e) e e' where- hoistError f = either (throwError . f) return . runExcept+-- @+-- 'hoistError' :: 'MonadError' err m -> (() -> err) -> 'Maybe' a -> m a+-- 'hoistError' :: 'MonadError' err m -> (e -> err) -> 'Either' e a -> m a+-- 'hoistError' :: 'MonadError' err m -> (e -> err) -> 'ExceptT' e m a -> m a+-- @+hoistError+ :: (PluckError e t m, MonadError e' m)+ => (e -> e')+ -> t a+ -> m a+hoistError f = foldError (throwError . f) pure -instance MonadError e' m => HoistError m (ExceptT e m) e e' where- hoistError f = either (throwError . f) return <=< runExceptT-#else--- 'ErrorT' was renamed to 'ExceptT' in mtl 2.2.2-instance MonadError e' m => HoistError m (ErrorT e m) e e' where- hoistError f = either (throwError . f) return <=< runErrorT-#endif+-- | @hoistError' = hoistError id@+--+-- @since 0.3.1.0+hoistError'+ :: (PluckError e t m, MonadError e m)+ => t a+ -> m a+hoistError' = hoistError id -- | A version of 'hoistError' that operates on values already in the monad. -- -- @--- 'hoistErrorM' :: 'MonadError' e m => (() -> e) -> m ('Maybe' a) -> m a--- 'hoistErrorM' :: 'MonadError' e m => (a -> e) -> m ('Either' a b) -> m b--- 'hoistErrorM' :: 'MonadError' e m => (a -> e) -> 'ExceptT' a m b -> 'ExceptT' a m b+-- 'hoistErrorM' :: 'MonadError' err m => (() -> err) -> m ('Maybe' a) -> m a+-- 'hoistErrorM' :: 'MonadError' err m => (e -> err) -> m ('Either' e b) -> m a+-- 'hoistErrorM' :: 'MonadError' err m => (e -> err) -> m 'ExceptT' e m b -> m a -- @ hoistErrorM- :: HoistError m t e e'+ :: (PluckError e t m, MonadError e' m) => (e -> e') -> m (t a) -> m a-hoistErrorM e m = do- x <- m- hoistError e x+hoistErrorM e m = m >>= hoistError e +-- | @hoistErrorM' = hoistErrorM id@+--+-- @since 0.3.1.0+hoistErrorM'+ :: (PluckError e t m, MonadError e m)+ => m (t a)+ -> m a+hoistErrorM' = hoistErrorM id++-- $mnemonics+--+-- The operators in this package are named according to a scheme:+--+-- * @('<?>')@ is the simplest error-handling function: it replaces+-- any error with its second argument.+--+-- * The additional @!@ in @('<!?>')@ and @('<%!?>')@ means the+-- operator handles values that are already "in a monad".+--+-- * The additional @%@ in @('<%?>')@ and @('<%!?>')@ means the+-- operator takes a function argument, which it applies to the error+-- from the partiality type. (The mnemonic is that @%@ sometimes+-- means "mod", and we abuse "mod" as a shorthand for "modify". It's a+-- long bow, but @lens@ uses the same mnemonic.)+ -- | A flipped synonym for 'hoistError'. -- -- @--- ('<%?>') :: 'MonadError' e m => 'Maybe' a -> (() -> e) -> m a--- ('<%?>') :: 'MonadError' e m => 'Either' a b -> (a -> e) -> m b--- ('<%?>') :: 'MonadError' e m => 'ExceptT' a m b -> (a -> e) -> 'ExceptT' a m b+-- ('<%?>') :: 'MonadError' err m => 'Maybe' a -> (() -> err) -> m a+-- ('<%?>') :: 'MonadError' err m => 'Either' e a -> (e -> err) -> m a+-- ('<%?>') :: 'MonadError' err m => 'ExceptT' e m a -> (e -> err) -> m a -- @ (<%?>)- :: HoistError m t e e'+ :: (PluckError e t m, MonadError e' m) => t a -> (e -> e') -> m a@@ -140,12 +140,12 @@ -- | A flipped synonym for 'hoistErrorM'. -- -- @--- ('<%!?>') :: 'MonadError' e m => m ('Maybe' a) -> (() -> e) -> m a--- ('<%!?>') :: 'MonadError' e m => m ('Either' a b) -> (a -> e) -> m b--- ('<%!?>') :: 'MonadError' e m => 'ExceptT' a m b -> (a -> e) -> 'ExceptT' a m b+-- ('<%!?>') :: 'MonadError' err m => m ('Maybe' a) -> (() -> err) -> m a+-- ('<%!?>') :: 'MonadError' err m => m ('Either' e a) -> (e -> err) -> m a+-- ('<%!?>') :: 'MonadError' err m => 'ExceptT' e m a -> (e -> err) -> 'ExceptT' e m a -- @ (<%!?>)- :: HoistError m t e e'+ :: (PluckError e t m, MonadError e' m) => m (t a) -> (e -> e') -> m a@@ -158,12 +158,12 @@ -- with a new one. -- -- @--- ('<?>') :: 'MonadError' e m => 'Maybe' a -> e -> m a--- ('<?>') :: 'MonadError' e m => 'Either' a b -> e -> m b--- ('<?>') :: 'MonadError' e m => 'ExceptT' a m b -> e -> 'ExceptT' a m b+-- ('<?>') :: 'MonadError' err m => 'Maybe' a -> err -> m a+-- ('<?>') :: 'MonadError' err m => 'Either' e a -> err -> m a+-- ('<?>') :: 'MonadError' err m => 'ExceptT' e m a -> err -> m a -- @ (<?>)- :: HoistError m t e e'+ :: (PluckError e t m, MonadError e' m) => t a -> e' -> m a@@ -175,12 +175,12 @@ -- | A version of '<?>' that operates on values already in the monad. -- -- @--- ('<!?>') :: 'MonadError' e m => m ('Maybe' a) -> e -> m a--- ('<!?>') :: 'MonadError' e m => m ('Either' a b) -> e -> m b--- ('<!?>') :: 'MonadError' e m => 'ExceptT' a m b -> e -> 'ExceptT' a m b+-- ('<!?>') :: 'MonadError' err m => m ('Maybe' a) -> e -> m a+-- ('<!?>') :: 'MonadError' err m => m ('Either' e a) -> e -> m a+-- ('<!?>') :: 'MonadError' err m => m 'ExceptT' e m a -> e -> m a -- @ (<!?>)- :: HoistError m t e e'+ :: (PluckError e t m, MonadError e' m) => m (t a) -> e' -> m a@@ -190,3 +190,29 @@ infixl 8 <!?> {-# INLINE (<!?>) #-}++-- | A class for plucking an error @e@ out of a partiality type @t@.+--+-- @since 0.3.0.0+class PluckError e t m | t -> e where+ pluckError :: t a -> m (Either e a)+ default pluckError :: Applicative m => t a -> m (Either e a)+ pluckError = foldError (pure . Left) (pure . Right)++ foldError :: (e -> m r) -> (a -> m r) -> t a -> m r+ default foldError :: Monad m => (e -> m r) -> (a -> m r) -> t a -> m r+ foldError f g = either f g <=< pluckError++ {-# MINIMAL pluckError | foldError #-}++instance (Applicative m, e ~ ()) => PluckError e Maybe m where+ pluckError = pure . maybe (Left ()) Right+ foldError f = maybe (f ())++instance Applicative m => PluckError e (Either e) m where+ pluckError = pure+ foldError = either++instance Monad m => PluckError e (ExceptT e m) m where+ pluckError = runExceptT+ foldError f g = either f g <=< runExceptT
+ src/Control/Monad/Fail/Hoist.hs view
@@ -0,0 +1,153 @@+{-# LANGUAGE FlexibleContexts #-}++-- | This module provides helpers for converting partiality types into+-- 'MonadFail' computations.+--+-- 'MonadFail''s purpose is to handle pattern-match failures in+-- @do@-expressions, and not to be a general-purpose error-handling+-- mechanism. Despite this, some libraries use it as one, and this+-- module can help you report errors via 'MonadFail'.+--+-- The operator mnemonics are the same as in+-- "Control.Monad.Error.Hoist", but with @#@ in place of @?@. You can+-- imagine a hastily-written @F@ looking kinda-sorta like a @#@, if it+-- helps.++module Control.Monad.Fail.Hoist+ ( hoistFail+ , hoistFail'+ , hoistFailM+ , hoistFailM'+ -- ** Operators+ , (<%#>)+ , (<%!#>)+ , (<#>)+ , (<!#>)+ ) where++import Control.Monad.Error.Hoist (PluckError(..))++-- | Given a conversion from the error in @t a@ to @String@, we can hoist the+-- computation into @m@.+--+-- @+-- 'hoistFail' :: 'MonadFail' m => (() -> String) -> 'Maybe' a -> m a+-- 'hoistFail' :: 'MonadFail' m => (e -> String) -> 'Either' e a -> m a+-- 'hoistFail' :: 'MonadFail' m => (e -> String) -> 'ExceptT' e m a -> m a+-- @+--+-- @since 0.3.0.0+hoistFail+ :: (PluckError e t m, MonadFail m)+ => (e -> String)+ -> t a+ -> m a+hoistFail f = foldError (fail . f) pure++-- | @hoistFail' = hoistFail id@+--+-- @since 0.3.0.0+hoistFail' :: (PluckError String t m, MonadFail m) => t a -> m a+hoistFail' = hoistFail id++-- | A version of 'hoistFail' that operates on values already in the monad.+--+-- @+-- 'hoistFailM' :: 'MonadFail' m => (() -> String) -> m ('Maybe' a) -> m a+-- 'hoistFailM' :: 'MonadFail' m => (e -> String) -> m ('Either' e a) -> m a+-- 'hoistFailM' :: 'MonadFail' m => (e -> String) -> m ('ExceptT' e m a) -> m a+-- @+--+-- @since 0.3.0.0+hoistFailM+ :: (PluckError e t m, MonadFail m)+ => (e -> String)+ -> m (t a)+ -> m a+hoistFailM f m = m >>= hoistFail f++-- | @hoistFailM' = hoistFailM id@+--+-- @since 0.3.0.0+hoistFailM'+ :: (PluckError String t m, MonadFail m)+ => m (t a)+ -> m a+hoistFailM' = hoistFailM id++-- | A flipped synonym for 'hoistFail'. Mnemonic: @#@ looks a bit like @F@.+--+-- @+-- ('<%#>') :: 'MonadFail' m => 'Maybe' a -> (() -> String) -> m a+-- ('<%#>') :: 'MonadFail' m => 'Either' e m a -> (e -> String) -> m a+-- ('<%#>') :: 'MonadFail' m => 'ExceptT' e m a -> (e -> String) -> m a+-- @+--+-- @since 0.3.0.0+(<%#>)+ :: (PluckError e t m, MonadFail m)+ => t a+ -> (e -> String)+ -> m a+(<%#>) = flip hoistFail++infixl 8 <%#>+{-# INLINE (<%#>) #-}++-- | A flipped synonym for 'hoistFailM'.+--+-- @+-- ('<%!#>') :: 'MonadFail' m => m ('Maybe' a) -> (() -> String) -> m a+-- ('<%!#>') :: 'MonadFail' m => m ('Either' e a) -> (e -> String) -> m a+-- ('<%!#>') :: 'MonadFail' m => m ('ExceptT' e m a) -> (e -> String) -> m a+-- @+--+-- @since 0.3.0.0+(<%!#>)+ :: (PluckError e t m, MonadFail m)+ => m (t a)+ -> (e -> String)+ -> m a+(<%!#>) = flip hoistFailM++infixl 8 <%!#>+{-# INLINE (<%!#>) #-}++-- | A version of '<%#>' that ignores the error in @t a@ and fails+-- with a new one.+--+-- @+-- ('<#>') :: 'MonadFail' m => 'Maybe' a -> String -> m a+-- ('<#>') :: 'MonadFail' m => 'Either' e a -> String -> m a+-- ('<#>') :: 'MonadFail' m => 'ExceptT' e m a -> String -> m a+-- @+--+-- @since 0.3.0.0+(<#>)+ :: (PluckError e t m, MonadFail m)+ => t a+ -> String+ -> m a+m <#> e = m <%#> const e++infixl 8 <#>+{-# INLINE (<#>) #-}++-- | A version of '<#>' that operates on values already in the monad.+--+-- @+-- ('<!#>') :: 'MonadFail' m => m ('Maybe' a) -> String -> m a+-- ('<!#>') :: 'MonadFail' m => m ('Either' e a) -> String -> m a+-- ('<!#>') :: 'MonadFail' m => m ('ExceptT' e m a) -> String -> m a+-- @+--+-- @since 0.3.0.0+(<!#>)+ :: (PluckError e t m, MonadFail m)+ => m (t a)+ -> String+ -> m a+m <!#> e = m >>= hoistFail (const e)++infixl 8 <!#>+{-# INLINE (<!#>) #-}