packages feed

hoist-error 0.2.1.0 → 0.3.1.0

raw patch · 4 files changed

Files

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 (<!#>) #-}