cleff 0.2.1.0 → 0.3.0.0
raw patch · 19 files changed
+529/−266 lines, 19 filesdep −QuickCheckdep ~basedep ~containersdep ~microlensPVP ok
version bump matches the API change (PVP)
Dependencies removed: QuickCheck
Dependency ranges changed: base, containers, microlens, template-haskell, th-abstraction, transformers-base, unliftio
API changes (from Hackage documentation)
- Cleff.Fail: instance (Cleff.Fail.Fail Cleff.Internal.Effect.:> es) => Control.Monad.Fail.MonadFail (Cleff.Internal.Monad.Eff es)
- Cleff.Internal.Base: instance (Cleff.Internal.Base.IOE Cleff.Internal.Effect.:> es) => Control.Monad.Base.MonadBase GHC.Types.IO (Cleff.Internal.Monad.Eff es)
- Cleff.Internal.Base: instance (Cleff.Internal.Base.IOE Cleff.Internal.Effect.:> es) => Control.Monad.Catch.MonadCatch (Cleff.Internal.Monad.Eff es)
- Cleff.Internal.Base: instance (Cleff.Internal.Base.IOE Cleff.Internal.Effect.:> es) => Control.Monad.Catch.MonadMask (Cleff.Internal.Monad.Eff es)
- Cleff.Internal.Base: instance (Cleff.Internal.Base.IOE Cleff.Internal.Effect.:> es) => Control.Monad.Catch.MonadThrow (Cleff.Internal.Monad.Eff es)
- Cleff.Internal.Base: instance (Cleff.Internal.Base.IOE Cleff.Internal.Effect.:> es) => Control.Monad.IO.Class.MonadIO (Cleff.Internal.Monad.Eff es)
- Cleff.Internal.Base: instance (Cleff.Internal.Base.IOE Cleff.Internal.Effect.:> es) => Control.Monad.IO.Unlift.MonadUnliftIO (Cleff.Internal.Monad.Eff es)
- Cleff.Internal.Base: instance (Cleff.Internal.Base.IOE Cleff.Internal.Effect.:> es) => Control.Monad.Primitive.PrimMonad (Cleff.Internal.Monad.Eff es)
- Cleff.Internal.Base: instance (Cleff.Internal.Base.IOE Cleff.Internal.Effect.:> es) => Control.Monad.Trans.Control.MonadBaseControl GHC.Types.IO (Cleff.Internal.Monad.Eff es)
- Cleff.Internal.Effect: infix 0 :>>
- Cleff.Internal.Effect: infixr 0 ~>
- Cleff.Internal.Effect: infixr 5 ++
- Cleff.Internal.Effect: type (:>) = Elem
- Cleff.Internal.Effect: type Effect = (Type -> Type) -> Type -> Type
- Cleff.Internal.Effect: type (f :: k -> Type) ~> (g :: k -> Type) = forall (a :: k). () => f a -> g a
- Cleff.Internal.Effect: type family (xs :: [a]) ++ (ys :: [a]) :: [a]
- Cleff.Mask: [BracketOnError] :: m a -> (a -> m c) -> (a -> m b) -> Mask m b
- Cleff.Mask: [Bracket] :: m a -> (a -> m c) -> (a -> m b) -> Mask m b
+ Cleff.Fail: instance (Cleff.Fail.Fail Cleff.Internal.Monad.:> es) => Control.Monad.Fail.MonadFail (Cleff.Internal.Monad.Eff es)
+ Cleff.Fresh: freshEnumToState :: Enum a => Eff (Fresh a : es) ~> Eff (State a : es)
+ Cleff.Internal.Base: instance (Cleff.Internal.Base.IOE Cleff.Internal.Monad.:> es) => Control.Monad.Base.MonadBase GHC.Types.IO (Cleff.Internal.Monad.Eff es)
+ Cleff.Internal.Base: instance (Cleff.Internal.Base.IOE Cleff.Internal.Monad.:> es) => Control.Monad.Catch.MonadCatch (Cleff.Internal.Monad.Eff es)
+ Cleff.Internal.Base: instance (Cleff.Internal.Base.IOE Cleff.Internal.Monad.:> es) => Control.Monad.Catch.MonadMask (Cleff.Internal.Monad.Eff es)
+ Cleff.Internal.Base: instance (Cleff.Internal.Base.IOE Cleff.Internal.Monad.:> es) => Control.Monad.Catch.MonadThrow (Cleff.Internal.Monad.Eff es)
+ Cleff.Internal.Base: instance (Cleff.Internal.Base.IOE Cleff.Internal.Monad.:> es) => Control.Monad.IO.Class.MonadIO (Cleff.Internal.Monad.Eff es)
+ Cleff.Internal.Base: instance (Cleff.Internal.Base.IOE Cleff.Internal.Monad.:> es) => Control.Monad.IO.Unlift.MonadUnliftIO (Cleff.Internal.Monad.Eff es)
+ Cleff.Internal.Base: instance (Cleff.Internal.Base.IOE Cleff.Internal.Monad.:> es) => Control.Monad.Primitive.PrimMonad (Cleff.Internal.Monad.Eff es)
+ Cleff.Internal.Base: instance (Cleff.Internal.Base.IOE Cleff.Internal.Monad.:> es) => Control.Monad.Trans.Control.MonadBaseControl GHC.Types.IO (Cleff.Internal.Monad.Eff es)
+ Cleff.Internal.Monad: infix 0 :>>
+ Cleff.Internal.Monad: infixr 0 ~>
+ Cleff.Internal.Monad: infixr 5 ++
+ Cleff.Internal.Monad: type (:>) = Elem
+ Cleff.Internal.Monad: type Effect = (Type -> Type) -> Type -> Type
+ Cleff.Internal.Monad: type (f :: k -> Type) ~> (g :: k -> Type) = forall (a :: k). () => f a -> g a
+ Cleff.Internal.Monad: type family (xs :: [a]) ++ (ys :: [a]) :: [a]
+ Cleff.Mask: [OnException] :: m a -> m b -> Mask m a
+ Cleff.Mask: bracketOnError_ :: Mask :> es => Eff es a -> Eff es c -> (a -> Eff es b) -> Eff es b
+ Cleff.Mask: onException :: Mask :> es => Eff es a -> Eff es b -> Eff es a
- Cleff.Error: catchError :: Error e_afOB :> es_afTz => Eff es_afTz a_afOA -> (e_afOB -> Eff es_afTz a_afOA) -> Eff es_afTz a_afOA
+ Cleff.Error: catchError :: Error e :> es => Eff es a -> (e -> Eff es a) -> Eff es a
- Cleff.Error: throwError :: Error e_afOw :> es_afTC => e_afOw -> Eff es_afTC a_afOy
+ Cleff.Error: throwError :: Error e :> es => e -> Eff es a
- Cleff.Fresh: fresh :: Fresh u_akl5 :> es_aklO => Eff es_aklO u_akl5
+ Cleff.Fresh: fresh :: Fresh u :> es => Eff es u
- Cleff.Input: input :: Input i_ajZM :> es_ak0v => Eff es_ak0v i_ajZM
+ Cleff.Input: input :: Input i :> es => Eff es i
- Cleff.Mask: bracket :: Mask :> es_ahLc => Eff es_ahLc a_XhId -> (a_XhId -> Eff es_ahLc c_XhIf) -> (a_XhId -> Eff es_ahLc b_ahIe) -> Eff es_ahLc b_ahIe
+ Cleff.Mask: bracket :: Mask :> es => Eff es a -> (a -> Eff es c) -> (a -> Eff es b) -> Eff es b
- Cleff.Mask: bracketOnError :: Mask :> es_ahL8 => Eff es_ahL8 a_XhIh -> (a_XhIh -> Eff es_ahL8 c_XhIj) -> (a_XhIh -> Eff es_ahL8 b_ahIi) -> Eff es_ahL8 b_ahIi
+ Cleff.Mask: bracketOnError :: Mask :> es => Eff es a -> (a -> Eff es c) -> (a -> Eff es b) -> Eff es b
- Cleff.Mask: mask :: Mask :> es_ahLi => ((~>) (Eff es_ahLi) (Eff es_ahLi) -> Eff es_ahLi a_ahI8) -> Eff es_ahLi a_ahI8
+ Cleff.Mask: mask :: Mask :> es => ((Eff es ~> Eff es) -> Eff es a) -> Eff es a
- Cleff.Mask: uninterruptibleMask :: Mask :> es_ahLg => ((~>) (Eff es_ahLg) (Eff es_ahLg) -> Eff es_ahLg a_ahIa) -> Eff es_ahLg a_ahIa
+ Cleff.Mask: uninterruptibleMask :: Mask :> es => ((Eff es ~> Eff es) -> Eff es a) -> Eff es a
- Cleff.Output: output :: Output o_alnz :> es_alol => o_alnz -> Eff es_alol ()
+ Cleff.Output: output :: Output o :> es => o -> Eff es ()
- Cleff.Reader: ask :: Reader r_ainM :> es_aipp => Eff es_aipp r_ainM
+ Cleff.Reader: ask :: Reader r :> es => Eff es r
- Cleff.Reader: local :: Reader r_ainO :> es_aipm => (r_ainO -> r_ainO) -> Eff es_aipm a_ainQ -> Eff es_aipm a_ainQ
+ Cleff.Reader: local :: Reader r :> es => (r -> r) -> Eff es a -> Eff es a
- Cleff.State: get :: State s_aiUI :> es_aiWU => Eff es_aiWU s_aiUI
+ Cleff.State: get :: State s :> es => Eff es s
- Cleff.State: put :: State s_aiUK :> es_aiWS => s_aiUK -> Eff es_aiWS ()
+ Cleff.State: put :: State s :> es => s -> Eff es ()
- Cleff.State: state :: State s_aiUM :> es_aiWQ => (s_aiUM -> (a_aiUN, s_aiUM)) -> Eff es_aiWQ a_aiUN
+ Cleff.State: state :: State s :> es => (s -> (a, s)) -> Eff es a
- Cleff.Trace: trace :: Trace :> es_alKz => String -> Eff es_alKz ()
+ Cleff.Trace: trace :: Trace :> es => String -> Eff es ()
- Cleff.Writer: listen :: Writer w_akDy :> es_akEZ => Eff es_akEZ a_XkDy -> Eff es_akEZ (a_XkDy, w_akDy)
+ Cleff.Writer: listen :: Writer w :> es => Eff es a -> Eff es (a, w)
- Cleff.Writer: tell :: Writer w_akDu :> es_akF1 => w_akDu -> Eff es_akF1 ()
+ Cleff.Writer: tell :: Writer w :> es => w -> Eff es ()
Files
- CHANGELOG.md +14/−1
- cleff.cabal +32/−34
- src/Cleff.hs +67/−33
- src/Cleff/Error.hs +66/−27
- src/Cleff/Fail.hs +3/−2
- src/Cleff/Fresh.hs +11/−5
- src/Cleff/Input.hs +14/−5
- src/Cleff/Internal/Base.hs +23/−17
- src/Cleff/Internal/Effect.hs +0/−33
- src/Cleff/Internal/Instances.hs +5/−8
- src/Cleff/Internal/Interpret.hs +34/−13
- src/Cleff/Internal/Monad.hs +67/−33
- src/Cleff/Internal/TH.hs +0/−1
- src/Cleff/Mask.hs +100/−20
- src/Cleff/Output.hs +15/−7
- src/Cleff/Reader.hs +19/−7
- src/Cleff/State.hs +28/−7
- src/Cleff/Trace.hs +12/−5
- src/Cleff/Writer.hs +19/−8
CHANGELOG.md view
@@ -1,12 +1,25 @@ # Changelog for `cleff` +## Unreleased++### Changed++- **[BREAKING]** Introduces an `OnException` primitive for `Mask` that replaces `Bracket` and `BracketOnError`+ (note that this only affects the effect datatype; there is still `bracket` and `bracketOnError` *functions* with the same semantics)+- `runError` and `mapError` are slightly (but observably) faster now++### Added++- `freshEnumToState` for `Fresh`+- `onException` and `bracketOnError_` for `Mask`+ ## 0.2.1.0 (2022-02-13) ### Added - Lifted convenience instances of `Bounded`, `Num`, `Fractional`, `Floating` and `IsString` for `Eff` - `MonadZip` instance from the `MonadComprehensions` extension for `Eff`-- `freshEnumToState` and `runFreshAtomicCounter` for `Fresh`+- `runFreshAtomicCounter` for `Fresh` - `inputToReader`, `mapInput` and `bindInput` for `Input` - `mapOutput` and `bindOutput` for `Output` - `runStateIORef`, `runStateMVar` and `runStateTVar` for `State`
cleff.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack name: cleff-version: 0.2.1.0+version: 0.3.0.0 synopsis: Fast and concise extensible effects description: Please see the README on GitHub at <https://github.com/re-xyr/cleff#readme> category: Control, Effect, Language@@ -45,7 +45,6 @@ Cleff.Input Cleff.Internal.Any Cleff.Internal.Base- Cleff.Internal.Effect Cleff.Internal.Instances Cleff.Internal.Interpret Cleff.Internal.Monad@@ -65,6 +64,8 @@ BlockArguments ConstraintKinds DataKinds+ DeriveAnyClass+ DerivingStrategies DerivingVia EmptyCase FlexibleContexts@@ -77,10 +78,10 @@ NoStarIsType PatternSynonyms PolyKinds- QuantifiedConstraints RankNTypes RoleAnnotations ScopedTypeVariables+ StandaloneDeriving TemplateHaskell TupleSections TypeApplications@@ -88,21 +89,20 @@ TypeOperators UndecidableInstances UnicodeSyntax- ViewPatterns ghc-options: -Wall -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-deriving-strategies -Wpartial-fields -Wunused-type-patterns -Wmissing-export-lists build-depends: atomic-primops ==0.8.*- , base >=4.12 && <5- , containers ==0.6.*+ , base >=4.12 && <4.17+ , containers >=0.5 && <0.7 , exceptions ==0.10.*- , microlens >=0.4.9 && <0.5+ , microlens >=0.1 && <0.5 , monad-control >=1 && <1.1 , primitive >=0.6 && <0.8 , rec-smallarray ==0.1.*- , template-haskell >=2.14 && <3- , th-abstraction >=0.2.11 && <0.5- , transformers-base >=0.4.5 && <0.5- , unliftio >=0.2.8 && <0.3+ , template-haskell >=2.14 && <2.19+ , th-abstraction >=0.2 && <0.5+ , transformers-base >=0.1 && <0.5+ , unliftio >=0.2.4 && <0.3 if flag(dynamic-ioe) cpp-options: -DDYNAMIC_IOE default-language: Haskell2010@@ -122,6 +122,8 @@ BlockArguments ConstraintKinds DataKinds+ DeriveAnyClass+ DerivingStrategies DerivingVia EmptyCase FlexibleContexts@@ -134,10 +136,10 @@ NoStarIsType PatternSynonyms PolyKinds- QuantifiedConstraints RankNTypes RoleAnnotations ScopedTypeVariables+ StandaloneDeriving TemplateHaskell TupleSections TypeApplications@@ -145,24 +147,22 @@ TypeOperators UndecidableInstances UnicodeSyntax- ViewPatterns- DeriveAnyClass ghc-options: -Wall -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-deriving-strategies -Wpartial-fields -Wunused-type-patterns -threaded -rtsopts -with-rtsopts=-N build-depends: atomic-primops ==0.8.*- , base >=4.12 && <5+ , base >=4.12 && <4.17 , cleff- , containers ==0.6.*+ , containers >=0.5 && <0.7 , exceptions ==0.10.* , extra- , microlens >=0.4.9 && <0.5+ , microlens >=0.1 && <0.5 , monad-control >=1 && <1.1 , primitive >=0.6 && <0.8 , rec-smallarray ==0.1.*- , template-haskell >=2.14 && <3- , th-abstraction >=0.2.11 && <0.5- , transformers-base >=0.4.5 && <0.5- , unliftio >=0.2.8 && <0.3+ , template-haskell >=2.14 && <2.19+ , th-abstraction >=0.2 && <0.5+ , transformers-base >=0.1 && <0.5+ , unliftio >=0.2.4 && <0.3 if flag(dynamic-ioe) cpp-options: -DDYNAMIC_IOE default-language: Haskell2010@@ -186,6 +186,8 @@ BlockArguments ConstraintKinds DataKinds+ DeriveAnyClass+ DerivingStrategies DerivingVia EmptyCase FlexibleContexts@@ -198,10 +200,10 @@ NoStarIsType PatternSynonyms PolyKinds- QuantifiedConstraints RankNTypes RoleAnnotations ScopedTypeVariables+ StandaloneDeriving TemplateHaskell TupleSections TypeApplications@@ -209,28 +211,24 @@ TypeOperators UndecidableInstances UnicodeSyntax- ViewPatterns- DeriveAnyClass- DeriveGeneric ghc-options: -Wall -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-deriving-strategies -Wpartial-fields -Wunused-type-patterns -threaded -rtsopts -with-rtsopts=-N build-depends:- QuickCheck- , atomic-primops ==0.8.*- , base >=4.12 && <5+ atomic-primops ==0.8.*+ , base >=4.12 && <4.17 , cleff- , containers ==0.6.*+ , containers >=0.5 && <0.7 , exceptions ==0.10.* , extra , hspec , lifted-base- , microlens >=0.4.9 && <0.5+ , microlens >=0.1 && <0.5 , monad-control >=1 && <1.1 , primitive >=0.6 && <0.8 , rec-smallarray ==0.1.*- , template-haskell >=2.14 && <3- , th-abstraction >=0.2.11 && <0.5- , transformers-base >=0.4.5 && <0.5- , unliftio >=0.2.8 && <0.3+ , template-haskell >=2.14 && <2.19+ , th-abstraction >=0.2 && <0.5+ , transformers-base >=0.1 && <0.5+ , unliftio >=0.2.4 && <0.3 if flag(dynamic-ioe) cpp-options: -DDYNAMIC_IOE default-language: Haskell2010
src/Cleff.hs view
@@ -9,7 +9,7 @@ -- This library implements an /extensible effects system/, where sets of monadic actions ("effects") are encoded as -- datatypes, tracked at the type level and can have multiple different implementations. This means you can swap out -- implementations of certain monadic actions in mock tests or in different environments. The notion of "effect" is--- general here: it can be an 'IO'-performing side effect, or just obtaining the value of a static global environment.+-- general here: it can be an 'IO'-performing side effect, or just reading the value of a static global environment. -- -- In particular, this library consists of --@@ -20,43 +20,77 @@ -- * Combinators for defining new effects and interpreting them /on your own/. These effects can be translated in terms -- of other already existing effects, or into operations in the 'IO' monad. ----- So, this library allows you to do two things:+-- In terms of structuring your application, this library helps you to do two things: -- -- * __Effect management:__ The 'Eff' monad tracks what effects are used explicitly at the type level, therefore you--- are able to be certain about what effects are involved in each function.--- * __Effect decoupling:__ You can decouple the implementation of the effects from your application and swap them--- easily.+-- are able to enforce what effects are involved in each function, and avoid accidentally introduced behaviors.+-- * __Effect decoupling:__ You can swap between the implementations of the effects in your application easily,+-- so you can refactor and test your applications with less clutter. module Cleff ( -- * Using effects- Eff, (:>), (:>>), Effect, IOE- , -- ** Running effects+ Eff+ , (:>)+ , (:>>)+ , Effect+ , IOE+ -- ** Running effects -- $runningEffects- runPure, runIOE- , -- * Defining effects+ , runPure+ , runIOE+ -- * Defining effects -- $definingEffects- send, sendVia, makeEffect, makeEffect_- , -- * Trivial effects handling- raise, raiseN, inject, subsume, subsumeN, KnownList, Subset- , -- * Interpreting effects+ , send+ , sendVia+ , makeEffect+ , makeEffect_+ -- * Trivial effects handling+ , raise+ , raiseN+ , inject+ , subsume+ , subsumeN+ , KnownList+ , Subset+ -- * Interpreting effects -- $interpretingEffects- Handler, interpret, reinterpret, reinterpret2, reinterpret3, reinterpretN, interpose, impose, imposeN- , -- ** Interpreting in terms of 'IO'- HandlerIO, interpretIO- , -- ** Translating effects- Translator, transform, translate- , -- ** Transforming interpreters- raiseUnder, raiseNUnder, raiseUnderN, raiseNUnderN- , -- * Combinators for interpreting higher order effects+ , Handler+ , interpret+ , reinterpret+ , reinterpret2+ , reinterpret3+ , reinterpretN+ , interpose+ , impose+ , imposeN+ -- ** Interpreting in terms of 'IO'+ , HandlerIO+ , interpretIO+ -- ** Translating effects+ , Translator+ , transform+ , translate+ -- ** Transforming interpreters+ , raiseUnder+ , raiseNUnder+ , raiseUnderN+ , raiseNUnderN+ -- * Combinators for interpreting higher order effects -- $higherOrderEffects- Handling, toEff, toEffWith, withFromEff- , -- ** Interpreting 'IO'-related higher order effects- withToIO, fromIO- , -- * Miscellaneous- type (~>), type (++), MonadIO (..), MonadUnliftIO (..)+ , Handling+ , toEff+ , toEffWith+ , withFromEff+ -- ** Interpreting 'IO'-related higher order effects+ , withToIO+ , fromIO+ -- * Miscellaneous+ , type (~>)+ , type (++)+ , MonadIO (..)+ , MonadUnliftIO (..) ) where import Cleff.Internal.Base-import Cleff.Internal.Effect import Cleff.Internal.Instances () import Cleff.Internal.Interpret import Cleff.Internal.Monad@@ -64,21 +98,21 @@ import UnliftIO (MonadIO (liftIO), MonadUnliftIO (withRunInIO)) -- $runningEffects--- To run an effect @T@, we should use an /interpreter/ of @T@, which is a function that has type like this:+-- To run an effect @T@, we should use an /interpreter/ of @T@, which is a function that has a type like this: -- -- @ -- runT :: 'Eff' (T ': es) a -> 'Eff' es a -- @ -- -- Such an interpreter provides an implementation of @T@ and eliminates @T@ from the effect stack. All builtin effects--- in @cleff@ have interpreters coming together with them.+-- in @cleff@ have interpreters out of the box in their respective modules. -- -- By applying interpreters to an 'Eff' computation, you can eventually obtain an /end computation/, where there are no--- more effects present on the effect stack. There are two kinds of end computations:+-- more effects to be interpreted on the effect stack. There are two kinds of end computations: ----- * A /pure computation/ with the type @'Eff' '[] a@, which you can obtain the value via 'Cleff.runPure'; or,--- * An /impure computation/ with type @'Eff' '['Cleff.IOE'] a@ that can be transformed into an IO computation via--- 'Cleff.runIOE'.+-- * A /pure computation/ with the type @'Eff' '[] a@, which you can obtain the value via 'runPure'; or,+-- * An /impure computation/ with type @'Eff' '['IOE'] a@ that can be unwrapped into an IO computation via+-- 'runIOE'. -- $definingEffects -- An effect should be defined as a GADT and have the kind 'Effect'. Each operation in the effect is a constructor of
src/Cleff/Error.hs view
@@ -9,51 +9,78 @@ module Cleff.Error ( -- * Effect Error (..)- , -- * Operations- throwError, catchError, fromEither, fromException, fromExceptionVia, fromExceptionEff, fromExceptionEffVia,- note, catchErrorJust, catchErrorIf, handleError, handleErrorJust, handleErrorIf, tryError, tryErrorJust- , -- * Interpretations- runError, mapError+ -- * Operations+ , throwError+ , catchError+ -- ** Other ways of throwing errors+ , fromEither+ , fromException+ , fromExceptionVia+ , fromExceptionEff+ , fromExceptionEffVia+ , note+ -- ** Other ways of handling errors+ , catchErrorJust+ , catchErrorIf+ , handleError+ , handleErrorJust+ , handleErrorIf+ , tryError+ , tryErrorJust+ -- * Interpretations+ , runError+ , mapError ) where import Cleff import Cleff.Internal.Any import Cleff.Internal.Base-import Control.Exception (Exception)-import Data.Bool (bool)-import Data.Unique (Unique, hashUnique, newUnique)-import qualified UnliftIO.Exception as Exc+import Control.Exception (Exception)+import Data.Atomics.Counter (AtomicCounter, incrCounter, newCounter)+import Data.Bool (bool)+import System.IO.Unsafe (unsafePerformIO)+import qualified UnliftIO.Exception as Exc -- * Effect --- | An effect capable of breaking out of current control flow by raising an exceptional value @e@. This effect roughly--- corresponds to the @MonadError@ typeclass and @ExceptT@ monad transformer in @mtl@.+-- | An effect capable of breaking out of current control flow by throwing an error of type @e@, and handling the+-- errors thrown from computations. This effect roughly corresponds to the @MonadError@ typeclass and @ExceptT@ monad+-- transformer in @mtl@. data Error e :: Effect where ThrowError :: e -> Error e m a CatchError :: m a -> (e -> m a) -> Error e m a -- * Operations -makeEffect ''Error+makeEffect_ ''Error +-- | Throw an error in the current computation.+throwError :: Error e :> es => e -> Eff es a++-- | Handle an error if one is thrown from a computation, and then return to normal control flow.+catchError :: Error e :> es+ => Eff es a -- ^ The computation that may throw errors+ -> (e -> Eff es a) -- ^ The handler that is called when an error is thrown+ -> Eff es a+ -- | Lift an 'Either' value into the 'Error' effect. fromEither :: Error e :> es => Either e a -> Eff es a fromEither = either throwError pure -- | Lift exceptions generated by an 'IO' computation into the 'Error' effect.-fromException :: ∀ e es a. (Exc.Exception e, '[Error e, IOE] :>> es) => IO a -> Eff es a+fromException :: ∀ e es a. (Exception e, '[Error e, IOE] :>> es) => IO a -> Eff es a fromException m = Exc.catch (liftIO m) (throwError @e) -- | Like 'fromException', but allows to transform the exception into another error type.-fromExceptionVia :: (Exc.Exception ex, '[Error er, IOE] :>> es) => (ex -> er) -> IO a -> Eff es a+fromExceptionVia :: (Exception ex, '[Error er, IOE] :>> es) => (ex -> er) -> IO a -> Eff es a fromExceptionVia f m = Exc.catch (liftIO m) (throwError . f) -- | Lift exceptions generated by an 'Eff' computation into the 'Error' effect.-fromExceptionEff :: ∀ e es a. (Exc.Exception e, '[Error e, IOE] :>> es) => Eff es a -> Eff es a+fromExceptionEff :: ∀ e es a. (Exception e, '[Error e, IOE] :>> es) => Eff es a -> Eff es a fromExceptionEff m = withRunInIO \unlift -> Exc.catch (unlift m) (unlift . throwError @e) -- | Like 'fromExceptionEff', but allows to transform the exception into another error type.-fromExceptionEffVia :: (Exc.Exception ex, '[Error er, IOE] :>> es) => (ex -> er) -> Eff es a -> Eff es a+fromExceptionEffVia :: (Exception ex, '[Error er, IOE] :>> es) => (ex -> er) -> Eff es a -> Eff es a fromExceptionEffVia f m = withRunInIO \unlift -> Exc.catch (unlift m) (unlift . throwError . f) -- | Try to extract a value from 'Maybe', throw an error otherwise.@@ -93,27 +120,37 @@ -- * Interpretations --- | Exception wrapper used in 'runError' in order not to conflate error types with exception types.-data ErrorExc = ErrorExc !Unique Any+type ExcUid = Int -instance Exception ErrorExc+-- | Exception wrapper used in 'runError' in order not to conflate error types with exception types.+data ErrorExc = ErrorExc {-# UNPACK #-} !ExcUid Any+ deriving anyclass (Exception) instance Show ErrorExc where showsPrec _ (ErrorExc uid _) =- ("Cleff.Error.runError: Escaped error (error UID hash: " <>) . shows (hashUnique uid) . ("). This is possibly due \+ ("Cleff.Error.runError: Escaped error (error UID: " <>) . shows uid . ("). This is possibly due \ \to trying to 'throwError' in a forked thread, or trying to 'wait' on an error-throwing 'Async' computation out \ \of the effect scope where it is created. Refer to the haddock of 'runError' for details on the caveats. If all \ \those shenanigans mentioned or other similar ones seem unlikely, please report this as a bug." <>) -catch' :: ∀ e m a. MonadUnliftIO m => Unique -> m a -> (e -> m a) -> m a-catch' eid m h = m `Exc.catch` \ex@(ErrorExc eid' e) -> if eid == eid' then h (fromAny e) else Exc.throwIO ex+catch' :: ∀ e m a. MonadUnliftIO m => ExcUid -> m a -> (e -> m a) -> m a+catch' eid m h = m `Exc.catch` \ex@(ErrorExc eid' e) ->+ if eid == eid' then h (fromAny e) else Exc.throwIO ex {-# INLINE catch' #-} -try' :: ∀ e m a. MonadUnliftIO m => Unique -> m a -> m (Either e a)+try' :: ∀ e m a. MonadUnliftIO m => ExcUid -> m a -> m (Either e a) try' eid m = catch' eid (Right <$> m) (pure . Left) {-# INLINE try' #-} -errorHandler :: Unique -> Handler (Error e) (IOE ': es)+excUidSource :: AtomicCounter+excUidSource = unsafePerformIO (newCounter 0)+{-# NOINLINE excUidSource #-}++newExcUid :: IO ExcUid+newExcUid = incrCounter 1 excUidSource+{-# INLINE newExcUid #-}++errorHandler :: ExcUid -> Handler (Error e) (IOE ': es) errorHandler eid = \case ThrowError e -> Exc.throwIO $ ErrorExc eid (toAny e) CatchError m' h' -> withToIO \toIO -> liftIO $ catch' eid (toIO m') (toIO . h')@@ -121,7 +158,9 @@ -- | Run an 'Error' effect. ----- __Caveat__: 'runError' is implemented with 'Exc.Exception's therefore inherits some of its unexpected behaviors.+-- === Caveats+--+-- 'runError' is implemented with 'Exc.Exception's therefore inherits some of its unexpected behaviors. -- Errors thrown in forked threads will /not/ be directly caught by 'catchError's in the parent thread. Instead it will -- incur an exception, and we won't be quite able to display the details of that exception properly at that point. -- Therefore please properly handle the errors in the forked threads separately.@@ -132,7 +171,7 @@ -- will again not be caught. The best choice is /not to pass @Async@ values around randomly/. runError :: ∀ e es a. Eff (Error e ': es) a -> Eff es (Either e a) runError m = thisIsPureTrustMe do- eid <- liftIO newUnique+ eid <- liftIO newExcUid try' eid $ reinterpret (errorHandler eid) m {-# INLINE runError #-} @@ -141,7 +180,7 @@ mapError f = thisIsPureTrustMe . reinterpret \case ThrowError e -> throwError $ f e CatchError m h -> do- eid <- liftIO newUnique+ eid <- liftIO newExcUid res <- try' @e eid $ toEffWith (errorHandler eid) m case res of Left e -> toEff (h e)
src/Cleff/Fail.hs view
@@ -9,8 +9,9 @@ module Cleff.Fail ( -- * Effect Fail (..)- , -- * Interpretations- runFail, runFailIO+ -- * Interpretations+ , runFail+ , runFailIO ) where import Cleff
src/Cleff/Fresh.hs view
@@ -8,10 +8,13 @@ module Cleff.Fresh ( -- * Effect Fresh (..)- , -- * Operations- fresh- , -- * Interpretations- freshIntToState, runFreshAtomicCounter, runFreshUnique+ -- * Operations+ , fresh+ -- * Interpretations+ , freshIntToState+ , freshEnumToState+ , runFreshAtomicCounter+ , runFreshUnique ) where import Cleff@@ -28,7 +31,10 @@ -- * Operations -makeEffect ''Fresh+makeEffect_ ''Fresh++-- | Obtain a fresh unique value.+fresh :: Fresh u :> es => Eff es u -- * Interpretations
src/Cleff/Input.hs view
@@ -8,10 +8,16 @@ module Cleff.Input ( -- * Effect Input (..)- , -- * Operations- input, inputs- , -- * Interpretations- runInputConst, inputToListState, inputToReader, runInputEff, mapInput, bindInput+ -- * Operations+ , input+ , inputs+ -- * Interpretations+ , runInputConst+ , inputToListState+ , inputToReader+ , runInputEff+ , mapInput+ , bindInput ) where import Cleff@@ -26,7 +32,10 @@ -- * Operations -makeEffect ''Input+makeEffect_ ''Input++-- | Read an input value from an input source.+input :: Input i :> es => Eff es i -- | Apply a function to the result of 'input'. inputs :: Input i :> es => (i -> i') -> Eff es i'
src/Cleff/Internal/Base.hs view
@@ -17,17 +17,21 @@ module Cleff.Internal.Base ( -- * The 'IOE' Effect IOE- , -- * Primitive 'IO' functions- primLiftIO, primUnliftIO- , -- * Unwrapping 'Eff'- thisIsPureTrustMe, runIOE, runPure- , -- * Effect interpretation- HandlerIO, interpretIO- , -- * Combinators for interpreting higher-order effects- withToIO, fromIO+ -- * Primitive 'IO' functions+ , primLiftIO+ , primUnliftIO+ -- * Unwrapping 'Eff'+ , thisIsPureTrustMe+ , runIOE+ , runPure+ -- * Effect interpretation+ , HandlerIO+ , interpretIO+ -- * Combinators for interpreting higher-order effects+ , withToIO+ , fromIO ) where -import Cleff.Internal.Effect import Cleff.Internal.Interpret import Cleff.Internal.Monad import Control.Monad.Base (MonadBase (liftBase))@@ -43,22 +47,24 @@ -- * The 'IOE' effect --- | The effect for lifting and unlifting the 'IO' monad, allowing you to use 'MonadIO', 'MonadUnliftIO', 'PrimMonad',--- 'MonadCatch', 'MonadThrow' and 'MonadMask' functionalities. This is the "final" effect that most effects eventually--- are interpreted into. For example, you can do:+-- | The effect capable of lifting and unlifting the 'IO' monad, allowing you to use 'MonadIO', 'MonadUnliftIO',+-- 'PrimMonad', 'MonadCatch', 'MonadThrow' and 'MonadMask' functionalities. This is the "final" effect that most+-- effects eventually are interpreted into. For example, you can do: -- -- @ -- log :: 'IOE' :> es => 'Eff' es () -- log = 'liftIO' ('putStrLn' "Test logging") -- @ ----- It is not recommended to use this effect in application code, as it is too liberal and allows arbitrary IO. Ideally,--- this is only used in interpreting more fine-grained effects.+-- It is not recommended to use this effect directly in application code, as it is too liberal and allows arbitrary IO,+-- therefore making it harder to do proper effect management. Ideally, this is only used in interpreting more+-- fine-grained effects. --+-- === Technical details+-- -- Note that this is /not/ a real effect and cannot be interpreted in any way besides 'thisIsPureTrustMe' and--- 'runIOE'. It is similar to Polysemy's @Final@ effect which also cannot be interpreted. This is mainly for--- performance concern, but also that there doesn't really exist reasonable interpretations other than the current one,--- given the underlying implementation of the 'Eff' monad.+-- 'runIOE'. This is mainly for performance concern, but also that there doesn't really exist reasonable+-- interpretations other than the current one, given the underlying implementation of the 'Eff' monad. -- -- 'IOE' can be a real effect though, and you can enable the @dynamic-ioe@ build flag to have that. However it is only -- for reference purposes and should not be used in production code.
− src/Cleff/Internal/Effect.hs
@@ -1,33 +0,0 @@-{-# OPTIONS_HADDOCK not-home #-}--- |--- Copyright: (c) 2021 Xy Ren--- License: BSD3--- Maintainer: xy.r@outlook.com--- Stability: unstable--- Portability: non-portable (GHC only)------ This module contains definitions of some basic types related to effects. You won't need this module directly;--- these functionalities are reexported in the "Cleff" module.------ __This is an /internal/ module and its API may change even between minor versions.__ Therefore you should be--- extra careful if you're to depend on this module.-module Cleff.Internal.Effect (Effect, (:>), (:>>), type (++), type (~>)) where--import Data.Kind (Constraint, Type)-import Data.Rec.SmallArray (Elem, type (++), type (~>))---- | The type of effects. An effect @e m a@ takes an effect monad type @m :: 'Type' -> 'Type'@ and a result type--- @a :: 'Type'@.-type Effect = (Type -> Type) -> Type -> Type---- | @e ':>' es@ means the effect @e@ is present in the effect stack @es@, and therefore can be used in an--- @'Cleff.Eff' es@ computation.-type (:>) = Elem-infix 0 :>---- | @xs ':>>' es@ means the list of effects @xs@ are all present in the effect stack @es@. This is a convenient type--- alias for @(e1 ':>' es, ..., en ':>' es)@.-type family xs :>> es :: Constraint where- '[] :>> _ = ()- (x ': xs) :>> es = (x :> es, xs :>> es)-infix 0 :>>
src/Cleff/Internal/Instances.hs view
@@ -13,15 +13,14 @@ -- extra careful if you're to depend on this module. module Cleff.Internal.Instances () where -import Cleff.Internal.Monad (Eff)+import Cleff.Internal.Monad (Eff (Eff)) import Control.Applicative (Applicative (liftA2)) import Control.Monad.Zip (MonadZip (munzip, mzipWith))+import Data.Monoid (Ap (Ap)) import Data.String (IsString (fromString)) -- | @since 0.2.1.0-instance Bounded a => Bounded (Eff es a) where- minBound = pure minBound- maxBound = pure maxBound+deriving via (Ap (Eff es) a) instance Bounded a => Bounded (Eff es a) -- | @since 0.2.1.0 instance Num a => Num (Eff es a) where@@ -61,12 +60,10 @@ atanh = fmap atanh -- | @since 0.2.1.0-instance Semigroup a => Semigroup (Eff es a) where- (<>) = liftA2 (<>)+deriving newtype instance Semigroup a => Semigroup (Eff es a) -- | @since 0.2.1.0-instance Monoid a => Monoid (Eff es a) where- mempty = pure mempty+deriving newtype instance Monoid a => Monoid (Eff es a) -- | @since 0.2.1.0 instance IsString a => IsString (Eff es a) where
src/Cleff/Internal/Interpret.hs view
@@ -15,22 +15,43 @@ -- extra careful if you're to depend on this module. module Cleff.Internal.Interpret ( -- * Trivial handling- adjust, raise, raiseN, inject, subsume, subsumeN, raiseUnder, raiseNUnder, raiseUnderN, raiseNUnderN- , -- * Handler types- Handling, esSend, Handler, Translator- , -- * Interpreting effects- interpret, reinterpret, reinterpret2, reinterpret3, reinterpretN, interpose, impose, imposeN- , -- * Translating effects- transform, translate- , -- * Combinators for interpreting higher effects- toEff, toEffWith, withFromEff+ adjust+ , raise+ , raiseN+ , inject+ , subsume+ , subsumeN+ , raiseUnder+ , raiseNUnder+ , raiseUnderN+ , raiseNUnderN+ -- * Handler types+ , Handling+ , esSend+ , Handler+ , Translator+ -- * Interpreting effects+ , interpret+ , reinterpret+ , reinterpret2+ , reinterpret3+ , reinterpretN+ , interpose+ , impose+ , imposeN+ -- * Translating effects+ , transform+ , translate+ -- * Combinators for interpreting higher effects+ , toEff+ , toEffWith+ , withFromEff ) where -import Cleff.Internal.Effect import Cleff.Internal.Monad-import Data.Rec.SmallArray (Rec, pattern (:++:))-import qualified Data.Rec.SmallArray as Rec-import Unsafe.Coerce (unsafeCoerce)+import Data.Rec.SmallArray (Rec, pattern (:++:))+import qualified Data.Rec.SmallArray as Rec+import Unsafe.Coerce (unsafeCoerce) -- * Trivial handling
src/Cleff/Internal/Monad.hs view
@@ -13,23 +13,60 @@ -- __This is an /internal/ module and its API may change even between minor versions.__ Therefore you should be -- extra careful if you're to depend on this module. module Cleff.Internal.Monad- ( -- * The 'Eff' monad- InternalHandler (InternalHandler, runHandler), Eff (Eff, unEff)- , -- * Effect environment- Env, HandlerPtr, emptyEnv, adjustEnv, allocaEnv, readEnv, writeEnv, replaceEnv, appendEnv, updateEnv- , -- * Performing effect operations- KnownList, Subset, send, sendVia+ ( -- * Basic types+ Effect+ , type (:>)+ , type (:>>)+ , type (~>)+ , type (++)+ -- * The 'Eff' monad+ , InternalHandler (InternalHandler, runHandler)+ , Eff (Eff, unEff)+ -- * Effect environment+ , Env+ , HandlerPtr+ , emptyEnv+ , adjustEnv+ , allocaEnv+ , readEnv+ , writeEnv+ , replaceEnv+ , appendEnv+ , updateEnv+ -- * Performing effect operations+ , KnownList+ , Subset+ , send+ , sendVia ) where import Cleff.Internal.Any-import Cleff.Internal.Effect-import Control.Applicative (Applicative (liftA2))-import Control.Monad.Fix (MonadFix (mfix))-import Data.IntMap.Strict (IntMap)-import qualified Data.IntMap.Strict as Map-import Data.Rec.SmallArray (KnownList, Rec, Subset, pattern (:~:))-import qualified Data.Rec.SmallArray as Rec+import Control.Applicative (Applicative (liftA2))+import Control.Monad.Fix (MonadFix (mfix))+import Data.IntMap.Strict (IntMap)+import qualified Data.IntMap.Strict as Map+import Data.Kind (Constraint, Type)+import Data.Rec.SmallArray (Elem, KnownList, Rec, Subset, pattern (:~:), type (++), type (~>))+import qualified Data.Rec.SmallArray as Rec +-- * Basic types++-- | The type of effects. An effect @e m a@ takes an effect monad type @m :: 'Type' -> 'Type'@ and a result type+-- @a :: 'Type'@.+type Effect = (Type -> Type) -> Type -> Type++-- | @e ':>' es@ means the effect @e@ is present in the effect stack @es@, and therefore can be used in an+-- @'Cleff.Eff' es@ computation.+type (:>) = Elem+infix 0 :>++-- | @xs ':>>' es@ means the list of effects @xs@ are all present in the effect stack @es@. This is a convenient type+-- alias for @(e1 ':>' es, ..., en ':>' es)@.+type family xs :>> es :: Constraint where+ '[] :>> _ = ()+ (x ': xs) :>> es = (x :> es, xs :>> es)+infix 0 :>>+ -- * The 'Eff' monad -- | The internal representation of effect handlers. This is just a natural transformation from the effect type@@ -37,19 +74,18 @@ -- -- In interpreting functions (see "Cleff.Internal.Interpret"), the user-facing 'Cleff.Handler' type is transformed into -- this type.-newtype InternalHandler e = InternalHandler- { runHandler :: ∀ es. e (Eff es) ~> Eff es }+newtype InternalHandler e = InternalHandler { runHandler :: ∀ es. e (Eff es) ~> Eff es } --- | The extensible effect monad. A monad @'Eff' es@ is capable of performing any effect in the /effect stack/ @es@,+-- | The extensible effects monad. The monad @'Eff' es@ is capable of performing any effect in the /effect stack/ @es@, -- which is a type-level list that holds all effects available. However, most of the times, for flexibility, @es@ -- should be a polymorphic type variable, and you should use the '(:>)' and '(:>>)' operators in constraints to -- indicate what effects are in the stack. For example, -- -- @--- 'Cleff.Reader.Reader' 'String' ':>' es, 'Cleff.State.State' 'Bool' ':>' es => 'Eff' es 'Integer'+-- ('Cleff.Reader.Reader' 'String' ':>' es, 'Cleff.State.State' 'Bool' ':>' es) => 'Eff' es 'Integer' -- @ ----- allows you to perform operations of the @'Cleff.Reader.Reader' 'String'@ effect and the @'Cleff.State.State' 'Bool'@+-- means you can perform operations of the @'Cleff.Reader.Reader' 'String'@ effect and the @'Cleff.State.State' 'Bool'@ -- effect in a computation returning an 'Integer'. type role Eff nominal representational newtype Eff es a = Eff { unEff :: Env es -> IO a }@@ -75,9 +111,10 @@ {-# INLINE liftA2 #-} instance Monad (Eff es) where+ -- no 'return', because the default impl is correct and it is going to be deprecated anyway Eff x >>= f = Eff \es -> x es >>= \x' -> unEff (f x') es {-# INLINE (>>=) #-}- (>>) = (*>)+ (>>) = (*>) -- More efficient, since the default is @x >> y = x >>= const y@ {-# INLINE (>>) #-} instance MonadFix (Eff es) where@@ -92,10 +129,7 @@ -- effect interpretation ('Cleff.reinterpretN') and the latter for local interpretation ('Cleff.toEffWith') in order to -- retain correct HO semantics. For more details on this see https://github.com/re-xyr/cleff/issues/5. type role Env nominal-data Env (es :: [Effect]) = Env- {-# UNPACK #-} !(Rec HandlerPtr es) -- ^ The array.- {-# UNPACK #-} !Int -- ^ The next memory address to allocate.- !(IntMap Any) -- ^ The simulated memory.+data Env (es :: [Effect]) = Env {-# UNPACK #-} !Int {-# UNPACK #-} !(Rec HandlerPtr es) !(IntMap Any) -- | A pointer to 'InternalHandler' in an 'Env'. type role HandlerPtr nominal@@ -103,42 +137,42 @@ -- | Create an empty 'Env' with no address allocated. emptyEnv :: Env '[]-emptyEnv = Env Rec.empty 0 Map.empty+emptyEnv = Env 0 Rec.empty Map.empty {-# INLINE emptyEnv #-} -- | Adjust the effect stack via an function over 'Rec'. adjustEnv :: ∀ es' es. (Rec HandlerPtr es -> Rec HandlerPtr es') -> Env es -> Env es'-adjustEnv f (Env re n mem) = Env (f re) n mem+adjustEnv f (Env n re mem) = Env n (f re) mem {-# INLINE adjustEnv #-} -- | Allocate a new, empty address for a handler. \( O(1) \). allocaEnv :: ∀ e es. Env es -> (# HandlerPtr e, Env es #)-allocaEnv (Env re n mem) = (# HandlerPtr n, Env re (succ n) mem #)+allocaEnv (Env n re mem) = (# HandlerPtr n, Env (n + 1) re mem #) {-# INLINE allocaEnv #-} -- | Read the handler a pointer points to. \( O(1) \). readEnv :: ∀ e es. Rec.Elem e es => Env es -> InternalHandler e-readEnv (Env re _ mem) = fromAny $ mem Map.! unHandlerPtr (Rec.index @e re)+readEnv (Env _ re mem) = fromAny $ mem Map.! unHandlerPtr (Rec.index @e re) {-# INLINE readEnv #-} -- | Overwrite the handler a pointer points to. \( O(1) \). writeEnv :: ∀ e es. HandlerPtr e -> InternalHandler e -> Env es -> Env es-writeEnv (HandlerPtr m) x (Env re n mem) = Env re n (Map.insert m (toAny x) mem)+writeEnv (HandlerPtr m) x (Env n re mem) = Env n re (Map.insert m (toAny x) mem) {-# INLINE writeEnv #-} -- | Replace the handler pointer of an effect in the stack. \( O(n) \). replaceEnv :: ∀ e es. Rec.Elem e es => HandlerPtr e -> InternalHandler e -> Env es -> Env es-replaceEnv (HandlerPtr m) x (Env re n mem) = Env (Rec.update @e (HandlerPtr m) re) n (Map.insert m (toAny x) mem)+replaceEnv (HandlerPtr m) x (Env n re mem) = Env n (Rec.update @e (HandlerPtr m) re) (Map.insert m (toAny x) mem) {-# INLINE replaceEnv #-} -- | Add a new effect to the stack with its corresponding handler pointer. \( O(n) \). appendEnv :: ∀ e es. HandlerPtr e -> InternalHandler e -> Env es -> Env (e ': es)-appendEnv (HandlerPtr m) x (Env re n mem) = Env (HandlerPtr m :~: re) n (Map.insert m (toAny x) mem)+appendEnv (HandlerPtr m) x (Env n re mem) = Env n (HandlerPtr m :~: re) (Map.insert m (toAny x) mem) {-# INLINE appendEnv #-} -- | Use the state of LHS as a newer version for RHS. \( O(1) \). updateEnv :: ∀ es es'. Env es' -> Env es -> Env es-updateEnv (Env _ n mem) (Env re' _ _) = Env re' n mem+updateEnv (Env n _ mem) (Env _ re' _) = Env n re' mem {-# INLINE updateEnv #-} -- * Performing effect operations@@ -149,8 +183,8 @@ send = sendVia id -- | Perform an action in another effect stack via a transformation to that stack; in other words, this function "maps"--- the effect operation from effect stack @es@ to @es'@. This is a generalization of 'send'; end users most likely--- won't need to use this.+-- the effect operation from effect stack @es@ to @es'@. This is a largely generalized version of 'send'; only use this+-- if you are sure about what you're doing. -- -- @ -- 'send' = 'sendVia' 'id'
src/Cleff/Internal/TH.hs view
@@ -15,7 +15,6 @@ -- extra careful if you're to depend on this module. module Cleff.Internal.TH (makeEffect, makeEffect_) where -import Cleff.Internal.Effect import Cleff.Internal.Monad import Control.Monad (join) import Data.Char (toLower)
src/Cleff/Mask.hs view
@@ -8,30 +8,97 @@ module Cleff.Mask ( -- * Effect Mask (..)- , -- * Operations- mask, uninterruptibleMask, bracket, bracketOnError, mask_, uninterruptibleMask_, bracket_, finally, onError- , -- * Interpretations- runMask+ -- * High-level operations+ , bracket+ , bracketOnError+ , bracket_+ , bracketOnError_+ , onError+ , finally+ -- * Primitive operations+ , mask+ , uninterruptibleMask+ , onException+ , mask_+ , uninterruptibleMask_+ -- * Interpretations+ , runMask ) where import Cleff import Cleff.Internal.Base-import qualified UnliftIO.Exception as Exc+import qualified Control.Exception as Exc -- * Effect --- | An effect capable of 'Exc.mask'ing and specifically, 'Exc.bracket'ing operations, /i.e./ allowing cleanup after--- operations that my raise exceptions.+-- | An effect capable of 'Exc.mask'ing and performing cleanup operations when an computation is interrupted. In+-- particular, this effects allows the use of 'bracket'.+--+-- === Technical details+--+-- Regarding the nuances of 'bracket' semantics, this effect uses the semantics of "UnliftIO.Exception" rather than+-- "Control.Exception". They are more sensible defaults and users can implement other semantics out of the primitive+-- operations if they want to. data Mask :: Effect where Mask :: ((m ~> m) -> m a) -> Mask m a UninterruptibleMask :: ((m ~> m) -> m a) -> Mask m a- Bracket :: m a -> (a -> m c) -> (a -> m b) -> Mask m b- BracketOnError :: m a -> (a -> m c) -> (a -> m b) -> Mask m b+ OnException :: m a -> m b -> Mask m a -- * Operations -makeEffect ''Mask+makeEffect_ ''Mask +-- | Prevents a computation from receiving asynchronous exceptions, i.e. being interrupted by another thread. Also+-- provides a function to restore receiving async exceptions for a computation.+--+-- However, some potentially blocking actions like @takeMVar@ can still be interrupted, and for them also not to be+-- interrupted in any case you'll need 'uninterruptibleMask'. See 'Control.Exception.mask' for details.+mask :: Mask :> es => ((Eff es ~> Eff es) -> Eff es a) -> Eff es a++-- | Prevents a computation from receiving asynchronous exceptions, even if there is an interruptible operation+-- (operations that potentially deadlocks or otherwise blocks indefinitely). Therefore this function is potentially+-- dangerous in the sense that it can make a thread both unresponsive and unkillable. See+-- 'Control.Exception.uninterruptibleMask' for details.+uninterruptibleMask :: Mask :> es => ((Eff es ~> Eff es) -> Eff es a) -> Eff es a++-- | Like 'onError', but without 'uninterruptibleMask'ing the cleanup action, making it possible that a cleanup action+-- is interrupted. Use 'onError' is usually the safer option.+onException :: Mask :> es+ => Eff es a -- ^ The main computation that may throw an exception+ -> Eff es b -- ^ The computation that runs when an exception is thrown+ -> Eff es a++-- | Run a computation that acquires a resource (@alloc@), then a main computation using that resource, then a cleanup+-- computation (@dealloc@). 'bracket' guarantees that @alloc@ and @dealloc@ will always run, regardless of whether an+-- exception is thrown in the main computation. Note that if an exception is thrown in the main computation, it will+-- be rethrown after 'bracket' finishes.+--+-- === Technical details+--+-- Note that this function uses @unliftio@ semantics: resource acquiring action is interruptibly 'mask'ed while+-- resource cleanup is 'uninterruptibleMask'ed. Most of the times, this will be what you want. Other functions in this+-- module use @unliftio@ semantics too.+bracket :: Mask :> es+ => Eff es a -- ^ The computation to run first, usually acquires a resource+ -> (a -> Eff es c) -- ^ The computation to run after the main computation, usually cleans up+ -> (a -> Eff es b) -- ^ The main computation that uses the resource+ -> Eff es b+bracket alloc dealloc action = mask \restore -> do+ res <- alloc+ ret <- restore (action res) `onError` dealloc res+ _ <- uninterruptibleMask_ (dealloc res)+ pure ret++-- | Like 'bracket', but only runs cleanup if an exception is thrown in the main computation.+bracketOnError :: Mask :> es+ => Eff es a -- ^ The computation to run first, usually acquires a resource+ -> (a -> Eff es c) -- ^ The computation to run when the main computation throws an exception, usually cleans up+ -> (a -> Eff es b) -- ^ The main computation that uses the resource+ -> Eff es b+bracketOnError alloc dealloc action = mask \restore -> do+ res <- alloc+ restore (action res) `onError` dealloc res+ -- | Variant of 'mask' that does not provide a restoring function. mask_ :: Mask :> es => Eff es a -> Eff es a mask_ m = mask \_ -> m@@ -44,21 +111,34 @@ bracket_ :: Mask :> es => Eff es a -> Eff es c -> (a -> Eff es b) -> Eff es b bracket_ ma = bracket ma . const --- | Attach a cleanup action that will always run to a potentially throwing computation.-finally :: Mask :> es => Eff es a -> Eff es b -> Eff es a-finally m mz = bracket_ (pure ()) mz (const m)+-- | Variant of 'bracketOnError' that does not pass the allocated resource to the cleanup action.+bracketOnError_ :: Mask :> es => Eff es a -> Eff es c -> (a -> Eff es b) -> Eff es b+bracketOnError_ ma = bracketOnError ma . const --- | Attach an action that runs if the main computation throws an exception.-onError :: Mask :> es => Eff es a -> Eff es b -> Eff es a-onError m mz = bracketOnError (pure ()) (const mz) (const m)+-- | Attach an action that runs if the main computation throws an exception. Note that this will rethrow the exception+-- instead of returning to normal control flow.+--+-- The cleanup action is guaranteed not to be interrupted halfways.+onError :: Mask :> es+ => Eff es a -- ^ The main computation that may throw an exception+ -> Eff es b -- ^ The computation that runs when an exception is thrown+ -> Eff es a+onError m n = m `onException` uninterruptibleMask_ n +-- | Attach a cleanup action that will always run after a potentially throwing computation.+finally :: Mask :> es+ => Eff es a -- ^ The main computation that may throw an exception+ -> Eff es b -- ^ The computation that runs after the main computation, regardless of whether an exception is thrown+ -> Eff es a+finally m mz = (m `onError` mz) <* uninterruptibleMask_ mz+ -- * Interpretations -- | Interpret the 'Mask' effect in terms of primitive 'IO' actions. runMask :: Eff (Mask ': es) ~> Eff es runMask = thisIsPureTrustMe . reinterpret \case- Mask f -> withToIO \toIO -> Exc.mask \restore -> toIO $ f (fromIO . restore . toIO)- UninterruptibleMask f -> withToIO \toIO -> Exc.uninterruptibleMask \restore -> toIO $ f (fromIO . restore . toIO)- Bracket ma mz m -> withToIO \toIO -> Exc.bracket (toIO ma) (toIO . mz) (toIO . m)- BracketOnError ma mz m -> withToIO \toIO -> Exc.bracketOnError (toIO ma) (toIO . mz) (toIO . m)+ Mask f -> withToIO \toIO -> Exc.mask \restore -> toIO $ f (fromIO . restore . toIO)+ UninterruptibleMask f -> withToIO \toIO -> Exc.uninterruptibleMask \restore -> toIO $ f (fromIO . restore . toIO)+ OnException m n -> withToIO \toIO -> toIO m `Exc.catch` \(e :: Exc.SomeException) ->+ Exc.try @Exc.SomeException (toIO n) *> Exc.throwIO e {-# INLINE runMask #-}
src/Cleff/Output.hs view
@@ -8,10 +8,15 @@ module Cleff.Output ( -- * Effect Output (..)- , -- * Operations- output- , -- * Interpretations- outputToListState, outputToWriter, ignoreOutput, runOutputEff, mapOutput, bindOutput+ -- * Operations+ , output+ -- * Interpretations+ , outputToListState+ , outputToWriter+ , ignoreOutput+ , runOutputEff+ , mapOutput+ , bindOutput ) where import Cleff@@ -20,14 +25,17 @@ -- * Effect --- | An effect that is capable of sending outputs, for example to a log file or an output stream.+-- | An effect that is capable of producing outputs, for example writing to a log file or an output stream. data Output o :: Effect where Output :: o -> Output o m () -- * Operations -makeEffect ''Output+makeEffect_ ''Output +-- | Produce an output value.+output :: Output o :> es => o -> Eff es ()+ -- * Interpretations -- | Run an 'Output' effect by accumulating a list. Note that outputs are being prepended to the head of the list, so@@ -63,7 +71,7 @@ Output x -> output $ f x {-# INLINE mapOutput #-} --- | Transform an 'Input' effect into another one already in the effect stack, by an effectful computation.+-- | Transform an 'Output' effect into another one already in the effect stack, by an effectful computation. -- -- @since 0.2.1.0 bindOutput :: Output o' :> es => (o -> Eff es o') -> Eff (Output o ': es) ~> Eff es
src/Cleff/Reader.hs view
@@ -8,10 +8,13 @@ module Cleff.Reader ( -- * Effect Reader (..)- , -- * Operations- ask, local, asks- , -- * Interpretations- runReader, magnify+ -- * Operations+ , ask+ , local+ , asks+ -- * Interpretations+ , runReader+ , magnify ) where import Cleff@@ -20,16 +23,25 @@ -- * Effect -- | An effect capable of providing an immutable environment @r@ that can be read. This roughly corresponds to the--- @MonadReader@ typeclass and @ReaderT@ monad transformer in the @mtl@ approach.+-- @MonadReader@ typeclass and @ReaderT@ monad transformer in the @mtl@ library. data Reader r :: Effect where Ask :: Reader r m r Local :: (r -> r) -> m a -> Reader r m a -- * Operations -makeEffect ''Reader+makeEffect_ ''Reader --- | Apply a function on the result of 'ask'.+-- | Obtain the environment value.+ask :: Reader r :> es => Eff es r++-- | Modify the environment value temporarily for a computation.+local :: Reader r :> es+ => (r -> r) -- ^ The function that modifies the environment+ -> Eff es a -- ^ The computation to run with the modified environment+ -> Eff es a++-- | Apply a function to the result of 'ask'. asks :: Reader r :> es => (r -> s) -> Eff es s asks = (<$> ask)
src/Cleff/State.hs view
@@ -8,10 +8,18 @@ module Cleff.State ( -- * Effect State (..)- , -- * Operations- get, put, state, gets, modify- , -- * Interpretations- runState, runStateIORef, runStateMVar, runStateTVar, zoom+ -- * Operations+ , get+ , put+ , state+ , gets+ , modify+ -- * Interpretations+ , runState+ , runStateIORef+ , runStateMVar+ , runStateTVar+ , zoom ) where import Cleff@@ -27,7 +35,7 @@ -- * Effect -- | An effect capable of providing a mutable state @s@ that can be read and written. This roughly corresponds to the--- @MonadState@ typeclass and @StateT@ monad transformer in the @mtl@ approach.+-- @MonadState@ typeclass and @StateT@ monad transformer in the @mtl@ library. data State s :: Effect where Get :: State s m s Put :: s -> State s m ()@@ -35,8 +43,19 @@ -- * Operations -makeEffect ''State+makeEffect_ ''State +-- | Read the current state.+get :: State s :> es => Eff es s++-- | Update the state with a new value.+put :: State s :> es => s -> Eff es ()++-- | Modify the state /and/ produce a value from the state via a function.+state :: State s :> es+ => (s -> (a, s)) -- ^ The function that takes the state and returns a result value together with a modified state+ -> Eff es a+ -- | Apply a function to the result of 'get'. gets :: State s :> es => (s -> t) -> Eff es t gets = (<$> get)@@ -56,7 +75,9 @@ -- | Run the 'State' effect. ----- __Caveat__: The 'runState' interpreter is implemented with 'Data.IORef.IORef's and there is no way to do arbitrary+-- === Caveats+--+-- The 'runState' interpreter is implemented with 'Data.IORef.IORef's and there is no way to do arbitrary -- atomic transactions. The 'state' operation is atomic though and it is implemented with 'atomicModifyIORefCAS', which -- can be faster than @atomicModifyIORef@ in contention. For any more complicated cases of atomicity, please build your -- own effect that uses either @MVar@s or @TVar@s based on your need.
src/Cleff/Trace.hs view
@@ -8,10 +8,14 @@ module Cleff.Trace ( -- * Effect Trace (..)- , -- * Operations- trace- , -- * Interpretations- runTraceHandle, runTraceStdout, runTraceStderr, ignoreTrace, traceToOutput+ -- * Operations+ , trace+ -- * Interpretations+ , runTraceHandle+ , runTraceStdout+ , runTraceStderr+ , ignoreTrace+ , traceToOutput ) where import Cleff@@ -26,7 +30,10 @@ -- * Operations -makeEffect ''Trace+makeEffect_ ''Trace++-- | Output a trace message.+trace :: Trace :> es => String -> Eff es () -- * Interpretations
src/Cleff/Writer.hs view
@@ -8,10 +8,13 @@ module Cleff.Writer ( -- * Effect Writer (..)- , -- * Operations- tell, listen, listens- , -- * Interpretations- runWriter, runWriterBatch+ -- * Operations+ , tell+ , listen+ , listens+ -- * Interpretations+ , runWriter+ , runWriterBatch ) where import Cleff@@ -22,8 +25,8 @@ -- * Effect --- | An effect capable of accumulating outputs. This roughly corresponds to the @MonadWriter@ typeclass and @WriterT@--- monad transformer in the @mtl@ approach.+-- | An effect capable of accumulating monoidal outputs. This roughly corresponds to the @MonadWriter@ typeclass and+-- @WriterT@ monad transformer in the @mtl@ library. -- -- However, note that this does not have a @pass@ operation as we are not sure what its semantics should be. In fact, -- the @pass@ semantics in @mtl@ is also unclear and will change when handlers are put in different orders. To avoid@@ -34,8 +37,14 @@ -- * Operations -makeEffect ''Writer+makeEffect_ ''Writer +-- | Produces an output that is appended to the accumulated value.+tell :: Writer w :> es => w -> Eff es ()++-- | Monitor the output of a computation, and return the output alongside the computation's result.+listen :: Writer w :> es => Eff es a -> Eff es (a, w)+ -- | Apply a function to the accumulated output of 'listen'. listens :: Writer w :> es => (w -> x) -> Eff es a -> Eff es (a, x) listens f m = do@@ -46,7 +55,9 @@ -- | Run a monoidal 'Writer' effect. ----- __Caveat__: Both 'runWriter' and 'listen's under 'runWriter' will stop taking care of writer operations done on+-- === Caveats+--+-- Both 'runWriter' and 'listen's under 'runWriter' will stop taking care of writer operations done on -- forked threads as soon as the main thread finishes its computation. Any writer operation done -- /before main thread finishes/ is still taken into account. runWriter :: ∀ w es a. Monoid w => Eff (Writer w ': es) a -> Eff es (a, w)