Copyright | (c) 2021 Xy Ren |
---|---|
License | BSD3 |
Maintainer | xy.r@outlook.com |
Stability | experimental |
Portability | non-portable (GHC only) |
Safe Haskell | Trustworthy |
Language | Haskell2010 |
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 reading the value of a static global environment.
In particular, this library consists of
- The
Eff
monad, which is the core of an extensible effects system. All effects are performed within it and it will be the "main" monad of your application. This monad tracks effects at the type level. - A set of predefined general effects, like
Reader
andState
that can be used out of the box. - 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.
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 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.
Synopsis
- data Eff es a
- type (:>) = Elem
- type family xs :>> es :: Constraint where ...
- type Effect = (Type -> Type) -> Type -> Type
- data IOE :: Effect
- runPure :: Eff '[] a -> a
- runIOE :: Eff '[IOE] ~> IO
- send :: e :> es => e (Eff es) ~> Eff es
- sendVia :: e :> es' => (Eff es ~> Eff es') -> e (Eff es) ~> Eff es'
- makeEffect :: Name -> Q [Dec]
- makeEffect_ :: Name -> Q [Dec]
- raise :: forall e es. Eff es ~> Eff (e ': es)
- raiseN :: forall es' es. KnownList es' => Eff es ~> Eff (es' ++ es)
- inject :: forall es' es. Subset es' es => Eff es' ~> Eff es
- subsume :: forall e es. e :> es => Eff (e ': es) ~> Eff es
- subsumeN :: forall es' es. Subset es' es => Eff (es' ++ es) ~> Eff es
- class KnownList (es :: [k])
- class KnownList es => Subset (es :: [k]) (es' :: [k])
- type Handler e es = forall esSend. Handling esSend e es => e (Eff esSend) ~> Eff es
- interpret :: forall e es. Handler e es -> Eff (e ': es) ~> Eff es
- reinterpret :: forall e' e es. Handler e (e' ': es) -> Eff (e ': es) ~> Eff (e' ': es)
- reinterpret2 :: forall e' e'' e es. Handler e (e' ': (e'' ': es)) -> Eff (e ': es) ~> Eff (e' ': (e'' ': es))
- reinterpret3 :: forall e' e'' e''' e es. Handler e (e' ': (e'' ': (e''' ': es))) -> Eff (e ': es) ~> Eff (e' ': (e'' ': (e''' ': es)))
- reinterpretN :: forall es' e es. KnownList es' => Handler e (es' ++ es) -> Eff (e ': es) ~> Eff (es' ++ es)
- interpose :: forall e es. e :> es => Handler e es -> Eff es ~> Eff es
- impose :: forall e' e es. e :> es => Handler e (e' ': es) -> Eff es ~> Eff (e' ': es)
- imposeN :: forall es' e es. (KnownList es', e :> es) => Handler e (es' ++ es) -> Eff es ~> Eff (es' ++ es)
- type HandlerIO e es = forall esSend. Handling esSend e es => e (Eff esSend) ~> IO
- interpretIO :: IOE :> es => HandlerIO e es -> Eff (e ': es) ~> Eff es
- type Translator e e' = forall esSend. e (Eff esSend) ~> e' (Eff esSend)
- transform :: forall e e' es. e' :> es => Translator e e' -> Eff (e ': es) ~> Eff es
- translate :: forall e e' es. Translator e e' -> Eff (e ': es) ~> Eff (e' ': es)
- raiseUnder :: forall e' e es. Eff (e ': es) ~> Eff (e ': (e' ': es))
- raiseNUnder :: forall es' e es. KnownList es' => Eff (e ': es) ~> Eff (e ': (es' ++ es))
- raiseUnderN :: forall e es' es. KnownList es' => Eff (es' ++ es) ~> Eff (es' ++ (e ': es))
- raiseNUnderN :: forall es'' es' es. (KnownList es', KnownList es'') => Eff (es' ++ es) ~> Eff (es' ++ (es'' ++ es))
- class Handling esSend e es | esSend -> e es
- toEff :: Handling esSend e es => Eff esSend ~> Eff es
- toEffWith :: forall esSend e es. Handling esSend e es => Handler e es -> Eff esSend ~> Eff es
- withFromEff :: Handling esSend e es => ((Eff es ~> Eff esSend) -> Eff esSend a) -> Eff es a
- withToIO :: (Handling esSend e es, IOE :> es) => ((Eff esSend ~> IO) -> IO a) -> Eff es a
- fromIO :: (Handling esSend e es, IOE :> es) => IO ~> Eff esSend
- type (~>) f g = forall a. f a -> g a
- type family xs ++ ys where ...
- class Monad m => MonadIO (m :: Type -> Type) where
- class MonadIO m => MonadUnliftIO (m :: Type -> Type) where
- withRunInIO :: ((forall a. m a -> IO a) -> IO b) -> m b
Using effects
The extensible effects monad. The monad
is capable of performing any effect in the effect stack Eff
eses
,
which is a type-level list that holds all effects available.
The best practice is to always use a polymorphic type variable for the effect stack es
, and then use the type
operators (:>)
and (:>>)
in constraints to indicate what effects are available in the stack. For example,
(Reader
String
:>
es,State
Bool
:>
es) =>Eff
esInteger
means you can perform operations of the
effect and the Reader
String
effect in a computation returning an State
Bool
Integer
. The reason why you should always use a polymorphic effect stack as
opposed to a concrete list of effects are that
- it can contain other effects that are used by computations other than the current one, and
- it does not require you to run the effects in any particular order.
Instances
IOE :> es => MonadBase IO (Eff es) Source # | Compatibility instance; use |
Defined in Cleff.Internal.Base | |
IOE :> es => MonadBaseControl IO (Eff es) Source # | Compatibility instance; use |
Monad (Eff es) Source # | |
Functor (Eff es) Source # | |
MonadFix (Eff es) Source # | |
Defined in Cleff.Internal.Monad | |
Fail :> es => MonadFail (Eff es) Source # | |
Defined in Cleff.Fail | |
Applicative (Eff es) Source # | |
MonadZip (Eff es) Source # | Compatibility instance for Since: 0.2.1.0 |
IOE :> es => MonadIO (Eff es) Source # | |
Defined in Cleff.Internal.Base | |
IOE :> es => MonadThrow (Eff es) Source # | |
Defined in Cleff.Internal.Base | |
IOE :> es => MonadCatch (Eff es) Source # | |
IOE :> es => MonadMask (Eff es) Source # | |
IOE :> es => PrimMonad (Eff es) Source # | |
IOE :> es => MonadUnliftIO (Eff es) Source # | |
Defined in Cleff.Internal.Base | |
Bounded a => Bounded (Eff es a) Source # | Since: 0.2.1.0 |
Floating a => Floating (Eff es a) Source # | Since: 0.2.1.0 |
Defined in Cleff.Internal.Instances sqrt :: Eff es a -> Eff es a # (**) :: Eff es a -> Eff es a -> Eff es a # logBase :: Eff es a -> Eff es a -> Eff es a # asin :: Eff es a -> Eff es a # acos :: Eff es a -> Eff es a # atan :: Eff es a -> Eff es a # sinh :: Eff es a -> Eff es a # cosh :: Eff es a -> Eff es a # tanh :: Eff es a -> Eff es a # asinh :: Eff es a -> Eff es a # acosh :: Eff es a -> Eff es a # atanh :: Eff es a -> Eff es a # log1p :: Eff es a -> Eff es a # expm1 :: Eff es a -> Eff es a # | |
Fractional a => Fractional (Eff es a) Source # | Since: 0.2.1.0 |
Num a => Num (Eff es a) Source # | Since: 0.2.1.0 |
IsString a => IsString (Eff es a) Source # | Since: 0.2.1.0 |
Defined in Cleff.Internal.Instances fromString :: String -> Eff es a # | |
Semigroup a => Semigroup (Eff es a) Source # | Since: 0.2.1.0 |
Monoid a => Monoid (Eff es a) Source # | Since: 0.2.1.0 |
type PrimState (Eff es) Source # | |
Defined in Cleff.Internal.Base | |
type StM (Eff es) a Source # | |
Defined in Cleff.Internal.Base |
type family xs :>> es :: Constraint where ... infix 0 Source #
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 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
. 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.
Running effects
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 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 to be interpreted on the effect stack. There are two kinds of end computations:
runPure :: Eff '[] a -> a Source #
Unwrap a pure Eff
computation into a pure value, given that all effects are interpreted.
Defining effects
An effect should be defined as a GADT and have the kind Effect
. Each operation in the effect is a constructor of
the effect type. For example, an effect supporting reading and writing files can be like this:
data Filesystem ::Effect
where ReadFile ::FilePath
-> Filesystem mString
WriteFile ::FilePath
->String
-> Filesystem m ()
Here, ReadFile
is an operation that takes a FilePath
and returns a String
, presumably the content of the file;
WriteFile
is an operation that takes a FilePath
and a String
and returns ()
, meaning it only performs
side effects - presumably writing the String
to the file specified.
Operations constructed with these constructors can be performed via the send
function. You can also use the
Template Haskell function makeEffect
to automatically generate definitions of functions that perform the effects.
send :: e :> es => e (Eff es) ~> Eff es Source #
Perform an effect operation, i.e. a value of an effect type e ::
. This requires Effect
e
to be in the
effect stack.
makeEffect :: Name -> Q [Dec] Source #
For a datatype T
representing an effect,
generates functions defintions for performing the
operations of makeEffect
TT
via send
. For example,
makeEffect
''Filesystem
generates the following definitions:
readFile :: Filesystem:>
es =>FilePath
->Eff
esString
readFile x =send
(ReadFile x) writeFile :: Filesystem:>
es =>FilePath
->String
->Eff
es () writeFile x y =send
(WriteFile x y)
The naming rule is changing the first uppercase letter in the constructor name to lowercase or removing the :
symbol in the case of operator constructors. Also, this function will preserve any fixity declarations defined on
the constructors.
Technical details
This function is also "weaker" than polysemy
's makeSem
, because this function cannot properly handle some
cases involving ambiguous types. Those cases are rare, though. See the ThSpec
test spec for more details.
makeEffect_ :: Name -> Q [Dec] Source #
Like makeEffect
, but doesn't generate type signatures. This is useful when you want to attach Haddock
documentation to the function signature, e.g.:
data Identity ::Effect
where Noop :: Identity m ()makeEffect_
''Identity -- | Perform nothing at all. noop :: Identity:>
es =>Eff
es ()
Be careful that the function signatures must be added after the makeEffect_
call.
Trivial effects handling
raise :: forall e es. Eff es ~> Eff (e ': es) Source #
Lift a computation into a bigger effect stack with one more effect. For a more general version see raiseN
.
raiseN :: forall es' es. KnownList es' => Eff es ~> Eff (es' ++ es) Source #
Lift a computation into a bigger effect stack with arbitrarily more effects. This function requires
TypeApplications
.
inject :: forall es' es. Subset es' es => Eff es' ~> Eff es Source #
Lift a computation with a fixed, known effect stack into some superset of the stack.
subsume :: forall e es. e :> es => Eff (e ': es) ~> Eff es Source #
Eliminate a duplicate effect from the top of the effect stack. For a more general version see subsumeN
.
subsumeN :: forall es' es. Subset es' es => Eff (es' ++ es) ~> Eff es Source #
Eliminate several duplicate effects from the top of the effect stack. This function requires TypeApplications
.
class KnownList (es :: [k]) Source #
means the list KnownList
eses
is concrete, i.e. is of the form '[a1, a2, ..., an]
instead of a type
variable.
class KnownList es => Subset (es :: [k]) (es' :: [k]) Source #
es
is a subset of es'
, i.e. all elements of es
are in es'
.
Instances
Subset ('[] :: [k]) (es :: [k]) Source # | |
Defined in Cleff.Internal.Rec reifyIndices :: [Int] | |
(Subset es es', Elem e es') => Subset (e ': es :: [k]) (es' :: [k]) Source # | |
Defined in Cleff.Internal.Rec reifyIndices :: [Int] |
Interpreting effects
An effect can be understood as the syntax of a tiny language; however we also need to define the meaning (or semantics) of the language. In other words, we need to specify the implementations of effects.
In an extensible effects system, this is achieved by writing effect handlers, which are functions that transforms operations of one effect into other "more primitive" effects. These handlers can then be used to make interpreters with library functions that we'll now see.
For example, for the Filesystem
effect:
data Filesystem ::Effect
where ReadFile ::FilePath
-> Filesystem mString
WriteFile ::FilePath
->String
-> Filesystem m ()
We can easily handle it in terms of IO
operations via interpretIO
, by pattern matching on the effect
constructors:
runFilesystemIO ::IOE
:>
es =>Eff
(Filesystem ': es) a ->Eff
es a runFilesystemIO =interpretIO
\case ReadFile path ->readFile
path WriteFile path contents ->writeFile
path contents
Specifically, a ReadFile
operation is mapped to a real readFile
IO computation, and similarly a WriteFile
operation is mapped to a writeFile
computation.
An effect is a set of abstract operations, and naturally, they can have more than one interpretations. Therefore,
here we can also construct an in-memory filesystem that reads from and writes into a State
effect, via
the reinterpret
function that adds another effect to the stack for the effect handler to use:
filesystemToState ::Fail
:>
es =>Eff
(Filesystem ': es) a ->Eff
(State
(Map
FilePath
String
) ': es) a filesystemToState =reinterpret
\case ReadFile path ->gets
(lookup
path) >>= \caseNothing
->fail
("File not found: " ++show
path)Just
contents ->pure
contents WriteFile path contents ->modify
(insert
path contents)
Here, we used the reinterpret
function to introduce a
as
the in-memory filesystem, making State
(Data.Map.Map' FilePath
String
)filesystemToState
a reinterpreter that "maps" an effect into another effect.
We also added a
constraint to our reinterpreter so that we're able to report errors.
To make an interpreter out of this is simple, as we just need to interpret the remaining Fail
:>
esState
effect:
runFilesystemPure ::Fail
:>
es =>Map
FilePath
String
->Eff
(Filesystem ': es) a ->Eff
es a runFilesystemPure fs =fmap
fst
-- runState returns (Eff es (a, s)), so we need to extract the first component to get (Eff es a) .runState
fs -- (State (Map FilePath String) ': es) ==> es .filesystemToState
-- (Filesystem ': es) ==> (State (Map FilePath String) ': es)
Both of these interpreters can then be applied to computations with the Filesystem
effect to give different
implementations to the effect.
type Handler e es = forall esSend. Handling esSend e es => e (Eff esSend) ~> Eff es Source #
The type of an effect handler, which is a function that transforms an effect e
from an arbitrary effect stack
into computations in the effect stack es
.
interpret :: forall e es. Handler e es -> Eff (e ': es) ~> Eff es Source #
Interpret an effect e
in terms of effects in the effect stack es
with an effect handler.
reinterpret :: forall e' e es. Handler e (e' ': es) -> Eff (e ': es) ~> Eff (e' ': es) Source #
Like interpret
, but adds a new effect e'
to the stack that can be used in the handler.
reinterpret2 :: forall e' e'' e es. Handler e (e' ': (e'' ': es)) -> Eff (e ': es) ~> Eff (e' ': (e'' ': es)) Source #
Like reinterpret
, but adds two new effects.
reinterpret3 :: forall e' e'' e''' e es. Handler e (e' ': (e'' ': (e''' ': es))) -> Eff (e ': es) ~> Eff (e' ': (e'' ': (e''' ': es))) Source #
Like reinterpret
, but adds three new effects.
reinterpretN :: forall es' e es. KnownList es' => Handler e (es' ++ es) -> Eff (e ': es) ~> Eff (es' ++ es) Source #
Like reinterpret
, but adds arbitrarily many new effects. This function requires TypeApplications
.
interpose :: forall e es. e :> es => Handler e es -> Eff es ~> Eff es Source #
Respond to an effect, but does not eliminate it from the stack. This means you can re-send the operations in the effect handler; it is often useful when you need to "intercept" operations so you can add extra behaviors like logging.
impose :: forall e' e es. e :> es => Handler e (e' ': es) -> Eff es ~> Eff (e' ': es) Source #
Like interpose
, but allows to introduce one new effect to use in the handler.
imposeN :: forall es' e es. (KnownList es', e :> es) => Handler e (es' ++ es) -> Eff es ~> Eff (es' ++ es) Source #
Like impose
, but allows introducing arbitrarily many effects. This requires TypeApplications
.
Interpreting in terms of IO
type HandlerIO e es = forall esSend. Handling esSend e es => e (Eff esSend) ~> IO Source #
The type of an IO
effect handler, which is a function that transforms an effect e
into IO
computations.
This is used for interpretIO
.
Translating effects
type Translator e e' = forall esSend. e (Eff esSend) ~> e' (Eff esSend) Source #
The type of a simple transformation function from effect e
to e'
.
Transforming interpreters
raiseUnder :: forall e' e es. Eff (e ': es) ~> Eff (e ': (e' ': es)) Source #
Like raise
, but adds the new effect under the top effect. This is useful for transforming an interpreter
e'
into a reinterpreter :>
es => Eff
(e ': es) ~>
Eff
es
:Eff
(e ': es) ~>
Eff
(e' ': es)
myInterpreter :: Bar:>
es =>Eff
(Foo ': es)~>
Eff
es myInterpreter = ... myReinterpreter ::Eff
(Foo ': es)~>
Eff
(Bar ': es) myReinterpreter = myInterpreter.
raiseUnder
In other words,
reinterpret
h ==interpret
h .raiseUnder
However, note that this function is suited for transforming an existing interpreter into a reinterpreter; if you
want to define a reinterpreter from scratch, you should still prefer reinterpret
, which is both easier to use and
more efficient.
Since: 0.2.0.0
raiseNUnder :: forall es' e es. KnownList es' => Eff (e ': es) ~> Eff (e ': (es' ++ es)) Source #
Like raiseUnder
, but allows introducing multiple effects. This function requires TypeApplications
.
Since: 0.2.0.0
raiseUnderN :: forall e es' es. KnownList es' => Eff (es' ++ es) ~> Eff (es' ++ (e ': es)) Source #
Like raiseUnder
, but allows introducing the effect under multiple effects. This function requires
TypeApplications
.
Since: 0.2.0.0
raiseNUnderN :: forall es'' es' es. (KnownList es', KnownList es'') => Eff (es' ++ es) ~> Eff (es' ++ (es'' ++ es)) Source #
A generalization of both raiseUnderN
and raiseNUnder
, allowing introducing multiple effects under multiple
effects. This function requires TypeApplications
and is subject to serious type ambiguity; you most likely will
need to supply all three type variables explicitly.
Since: 0.2.0.0
Combinators for interpreting higher order effects
Higher order effects are effects whose operations take other effect computations as arguments. For example, the
Error
effect is a higher order effect, because its CatchError
operation takes an effect
computation that may throw errors and also an error handler that returns an effect computation:
data Error e :: Effect
where
ThrowError :: e -> Error e m a
CatchError :: m a -> (e -> m a) -> Error e m a
More literally, an high order effect makes use of the monad type paramenter m
, while a first order effect, like
State
, does not.
It is harder to write interpreters for higher order effects, because the operations of these effects carry computations from arbitrary effect stacks, and we'll need to convert the to the current effect stack that the effect is being interpreted into. Fortunately, Cleff provides convenient combinators for doing so.
In a Handler
, you can temporarily "unlift" a computation from an arbitrary effect stack into the current stack via
toEff
, explicitly change the current effect interpretation in the computation via toEffWith
, or directly express
the effect in terms of IO
via withToIO
.
class Handling esSend e es | esSend -> e es Source #
The typeclass that denotes a handler scope, handling effect e
sent from the effect stack esSend
in the
effect stack es
.
You should not define instances for this typeclass whatsoever.
toEff :: Handling esSend e es => Eff esSend ~> Eff es Source #
Run a computation in the current effect stack; this is useful for interpreting higher-order effects. For example,
if you want to interpret a bracketing effects in terms of IO
:
data Resource m a where Bracket :: m a -> (a -> m ()) -> (a -> m b) -> Resource m b
You will not be able to simply write this for the effect:
runBracket :: IOE:>
es =>Eff
(Resource ': es) a ->Eff
es a runBracket =interpret
\case Bracket alloc dealloc use -> UnliftIO.bracket
alloc dealloc use
This is because effects are sended from all kinds of stacks that has Resource
in it, so effect handlers received
the effect as Resource esSend a
, where esSend
is an arbitrary stack with Resource
, instead of
Resource es a
. This means alloc
, dealloc
and use
are of type
, while Eff
esSend abracket
can
only take and return
. So we need to use Eff
es atoEff
, which converts an
into
an Eff
esSend a
:Eff
es a
runBracket :: IOE:>
es =>Eff
(Resource ': es) a ->Eff
es a runBracket =interpret
\case Bracket alloc dealloc use -> UnliftIO.bracket
(toEff
alloc) (toEff
. dealloc) (toEff
. use)
toEffWith :: forall esSend e es. Handling esSend e es => Handler e es -> Eff esSend ~> Eff es Source #
Run a computation in the current effect stack, just like toEff
, but takes a Handler
of the current effect
being interpreted, so that inside the computation being ran, the effect is interpreted differently. This is useful
for interpreting effects with local contexts, like Local
:
runReader :: r ->Eff
(Reader
r ': es)~>
Eff
es runReader x =interpret
(handle x) where handle :: r ->Handler
(Reader
r) es handle r = \caseAsk
->pure
rLocal
f m ->toEffWith
(handle $ f r) m
withFromEff :: Handling esSend e es => ((Eff es ~> Eff esSend) -> Eff esSend a) -> Eff es a Source #
Interpreting IO
-related higher order effects
withToIO :: (Handling esSend e es, IOE :> es) => ((Eff esSend ~> IO) -> IO a) -> Eff es a Source #
Temporarily gain the ability to unlift an
computation into Eff
esSendIO
. This is analogous to
withRunInIO
, and is useful in dealing with higher-order effects that involves IO
. For example, the Resource
effect that supports brecketing:
data Resource m a where Bracket :: m a -> (a -> m ()) -> (a -> m b) -> Resource m b
can be interpreted into bracket
actions in IO
, by converting all effect computations into
IO
compucations via withToIO
:
runResource ::IOE
:>
es =>Eff
(Resource ': es) a ->Eff
es a runResource =interpret
\case Bracket alloc dealloc use ->withToIO
$ \toIO ->bracket
(toIO alloc) (toIO . dealloc) (toIO . use)
fromIO :: (Handling esSend e es, IOE :> es) => IO ~> Eff esSend Source #
Lift an IO
computation into
. This is analogous to Eff
esSendliftIO
, and is only useful in dealing with
effect operations with the monad type in the negative position, for example mask
ing:
data Mask ::Effect
where Mask :: ((m~>
m) -> m a) -> Mask m a ^ this "m" is in negative position
See how the restore :: IO a -> IO a
from mask
is "wrapped" into
:Eff
esSend a -> Eff
esSend a
runMask ::IOE
:>
es =>Eff
(Mask ': es) a ->Eff
es a runMask =interpret
\case Mask f ->withToIO
$ \toIO ->mask
$ \restore -> f (fromIO
. restore . toIO)
Here, toIO
from withToIO
takes an
to Eff
esSendIO
, where it can be passed into the restore
function,
and the returned IO
computation is recovered into Eff
with fromIO
.
Miscellaneous
class Monad m => MonadIO (m :: Type -> Type) where #
Monads in which IO
computations may be embedded.
Any monad built by applying a sequence of monad transformers to the
IO
monad will be an instance of this class.
Instances should satisfy the following laws, which state that liftIO
is a transformer of monads:
Instances
class MonadIO m => MonadUnliftIO (m :: Type -> Type) where #
Monads which allow their actions to be run in IO
.
While MonadIO
allows an IO
action to be lifted into another
monad, this class captures the opposite concept: allowing you to
capture the monadic context. Note that, in order to meet the laws
given below, the intuition is that a monad must have no monadic
state, but may have monadic context. This essentially limits
MonadUnliftIO
to ReaderT
and IdentityT
transformers on top of
IO
.
Laws. For any value u
returned by askUnliftIO
, it must meet the
monad transformer laws as reformulated for MonadUnliftIO
:
unliftIO u . return = return
unliftIO u (m >>= f) = unliftIO u m >>= unliftIO u . f
Instances of MonadUnliftIO
must also satisfy the idempotency law:
askUnliftIO >>= \u -> (liftIO . unliftIO u) m = m
This law showcases two properties. First, askUnliftIO
doesn't change
the monadic context, and second, liftIO . unliftIO u
is equivalent to
id
IF called in the same monadic context as askUnliftIO
.
Since: unliftio-core-0.1.0.0
withRunInIO :: ((forall a. m a -> IO a) -> IO b) -> m b #
Convenience function for capturing the monadic context and running an IO
action with a runner function. The runner function is used to run a monadic
action m
in IO
.
Since: unliftio-core-0.1.0.0
Instances
MonadUnliftIO IO | |
Defined in Control.Monad.IO.Unlift | |
IOE :> es => MonadUnliftIO (Eff es) Source # | |
Defined in Cleff.Internal.Base | |
MonadUnliftIO m => MonadUnliftIO (ReaderT r m) | |
Defined in Control.Monad.IO.Unlift | |
MonadUnliftIO m => MonadUnliftIO (IdentityT m) | |
Defined in Control.Monad.IO.Unlift |