Safe Haskell | Safe-Inferred |
---|---|
Language | Haskell2010 |
Synopsis
- data Eff (es :: [Effect]) a
- type Effect = (Type -> Type) -> Type -> Type
- data Dispatch
- type family DispatchOf (e :: Effect) :: Dispatch
- class (e :: Effect) :> (es :: [Effect])
- type family xs :>> es :: Constraint where ...
- runPureEff :: HasCallStack => Eff '[] a -> a
- runEff :: HasCallStack => Eff '[IOE] a -> IO a
- data IOE :: Effect
- data UnliftStrategy
- data Persistence
- data Limit
- unliftStrategy :: IOE :> es => Eff es UnliftStrategy
- withUnliftStrategy :: IOE :> es => UnliftStrategy -> Eff es a -> Eff es a
- withSeqEffToIO :: (HasCallStack, IOE :> es) => ((forall r. Eff es r -> IO r) -> IO a) -> Eff es a
- withEffToIO :: (HasCallStack, IOE :> es) => UnliftStrategy -> ((forall r. Eff es r -> IO r) -> IO a) -> Eff es a
- withConcEffToIO :: (HasCallStack, IOE :> es) => Persistence -> Limit -> ((forall r. Eff es r -> IO r) -> IO a) -> Eff es a
- raise :: Eff es a -> Eff (e : es) a
- raiseWith :: HasCallStack => UnliftStrategy -> ((forall r. Eff (e : es) r -> Eff es r) -> Eff es a) -> Eff (e : es) a
- subsume :: e :> es => Eff (e : es) a -> Eff es a
- inject :: Subset subEs es => Eff subEs a -> Eff es a
- class KnownPrefix es => Subset (subEs :: [Effect]) (es :: [Effect])
- 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
Introduction
Haskell is one of the few programming languages that distinguishes between pure functions and functions that might perform side effects. For example, a function
f ::Int
->String
can't perform side effects at all, but a function
f ::Int
->IO
String
can perform any side effect. This "all or nothing" approach isn't very satisfactory though, because the vast majority of time we would like to signify that a function can perform some side effects, e.g. only be able to log messages.
This library provides support for expressing exactly that with its Eff
monad:
f :: Log:>
es =>Int
->Eff
esString
It implements support for extensible effects with both dynamic and static dispatch. For more information about each type consult the documentation in Effectful.Dispatch.Dynamic and Effectful.Dispatch.Static (when in doubt, start with dynamic dispatch).
The library provides:
- The
Eff
monad that tracks effects at the type level. This is going to be the main monad of your application. - A set of predefined, basic effects such as
Error
,Reader
,State
andWriter
. - Utilities for defining new effects and interpreting them, possibly in terms of already existing ones (see introduction of the Effectful.Dispatch.Dynamic module for more information).
While basic effects can be used out of the box, in general it's recommended to create your own that serve a more specific purpose.
Integration with existing libraries
Integration with most of existing libraries and frameworks can be done quite easily. The main difference in how that looks like depends on the way a library operates in a monadic context.
There are three main groups a library might fall into. It either operates:
1) In a monad of your application transformed by a library specific monad transformer.
2) In its own, concrete monad, which is usually IO
or a couple of monad
transformers on top of IO
.
3) In a polymorphic monad, which is constrained by a type class that implements core operations of a library.
Each case needs a slightly different approach to integrate with the Eff
monad.
Transformed monads
These are libraries that provide a custom transformer for the main monad of
your application and their operations make use of it for their
operations. Examples include InputT
from the
haskeline package or
ConduitT
from the conduit
package.
These libraries can trivially be used with the Eff
monad since it provides
typical instances that these libraries require the underlying monad to have,
such as MonadMask
or MonadUnliftIO
.
In case the Eff
monad doesn't provide a specific instance out of the box,
it can be supplied via an effect. As an example see how the instance of
MonadResource
for Eff
is implemented in the
resourcet-effectful
package.
Concrete monads
IO
If a library operates in IO
, there are a couple of ways to integrate it.
The easiest way is to use its functions selectively in the Eff
monad with
the help of liftIO
or withEffToIO
. However, this is not particularly
robust, since it vastly broadens the scope in which the IOE
effect is
needed (not to mention that explicit lifting is annoying).
A somewhat better approach is to create a dummy static effect with
lightweight wrappers of the library functions. As an example have a look at
the
Effectful.Concurrent.Async
module from the effectful
package that wraps the API of the
async package. Unfortunately,
this requires the amount of work proportional to the size of the library and
might not be the best option, especially if you only need to make use of a
tiny portion of the API.
Even better (though sometimes hard to do in practice) way is to consider, what do you need the library for and then create a custom effect with high level operations that the library in question will help us implement. The advantage of this approach is that we're hiding implementation details from the so-called "business logic" of our application and make it possible to easily swap them in different environments or during future refactoring.
Other
Some libraries operate in a transformer stack over IO
or have its own
concrete monad that's a newtype over IO
, e.g. Handler
from the
servant-server package.
In such case it's best to mirror the monad in question by the Eff
monad
with appropriate effects (as most popular monad transformers have subtle
issues),
use it as soon as possible, then at the end feed the final state to the monad
of the library so it proceeds as if nothing unusual happened.
As an example, consider the following monad:
>>>
import Control.Monad.State qualified as T
>>>
import Control.Monad.Except qualified as T
>>>
data HandlerState
>>>
data HandlerError
>>>
:{
newtype Handler a = Handler (T.ExceptT HandlerError (T.StateT HandlerState IO) a) deriving ( Applicative, Functor, Monad, MonadIO , T.MonadState HandlerState, T.MonadError HandlerError ) :}
This is how you can execute Eff
actions in the Handler
monad:
>>>
import Effectful.Error.Static
>>>
import Effectful.State.Static.Local
>>>
:{
effToHandler :: Eff [Error HandlerError, State HandlerState, IOE] a -> Handler a effToHandler m = do -- Retrieve the current state of the Handler. s <- T.get -- Run the Eff monad with effects mirroring the capabilities of @Handler@. (er, s') <- liftIO . runEff . runState s . runErrorNoCallStack @HandlerError $ m -- Update the state of the Handler and throw an error if appropriate. T.put s' either T.throwError pure er :}
Polymorphic monads
Libraries working in a polymorphic monad use mtl
style effects. Details
about their integration with the Eff
monad require familiarity with
dynamically dispatched effects and thus are available in the
Effectful.Dispatch.Dynamic module.
The Eff
monad
data Eff (es :: [Effect]) a Source #
The Eff
monad provides the implementation of a computation that performs
an arbitrary set of effects. In
, Eff
es aes
is a type-level list that
contains all the effects that the computation may perform. For example, a
computation that produces an Integer
by consuming a String
from the
global environment and acting upon a single mutable value of type Bool
would have the following type:
(Reader
String
:>
es,State
Bool
:>
es) =>Eff
esInteger
Abstracting over the list of effects with (:>)
:
- Allows the computation to be used in functions that may perform other effects.
- Allows the effects to be handled in any order.
Instances
IOE :> es => MonadBaseControl IO (Eff es) Source # | Instance included for compatibility with existing code. Usage of Note: the unlifting strategy for |
IOE :> es => MonadBase IO (Eff es) Source # | Instance included for compatibility with existing code. Usage of |
Defined in Effectful.Internal.Monad | |
Fail :> es => MonadFail (Eff es) Source # | |
Defined in Effectful.Internal.Monad | |
MonadFix (Eff es) Source # | |
Defined in Effectful.Internal.Monad | |
IOE :> es => MonadIO (Eff es) Source # | |
Defined in Effectful.Internal.Monad | |
NonDet :> es => Alternative (Eff es) Source # | Since: 2.2.0.0 |
Applicative (Eff es) Source # | |
Functor (Eff es) Source # | |
Monad (Eff es) Source # | |
NonDet :> es => MonadPlus (Eff es) Source # | Since: 2.2.0.0 |
MonadCatch (Eff es) Source # | |
Defined in Effectful.Internal.Monad | |
MonadMask (Eff es) Source # | |
Defined in Effectful.Internal.Monad mask :: HasCallStack => ((forall a. Eff es a -> Eff es a) -> Eff es b) -> Eff es b # uninterruptibleMask :: HasCallStack => ((forall a. Eff es a -> Eff es a) -> Eff es b) -> Eff es b # generalBracket :: HasCallStack => Eff es a -> (a -> ExitCase b -> Eff es c) -> (a -> Eff es b) -> Eff es (b, c) # | |
MonadThrow (Eff es) Source # | |
Defined in Effectful.Internal.Monad throwM :: (HasCallStack, Exception e) => e -> Eff es a # | |
Prim :> es => PrimMonad (Eff es) Source # | |
IOE :> es => MonadUnliftIO (Eff es) Source # | Instance included for compatibility with existing code. Usage of Note: the unlifting strategy for |
Defined in Effectful.Internal.Monad | |
Monoid a => Monoid (Eff es a) Source # | |
Semigroup a => Semigroup (Eff es a) Source # | |
type PrimState (Eff es) Source # | |
Defined in Effectful.Internal.Monad | |
type StM (Eff es) a Source # | |
Defined in Effectful.Internal.Monad |
Effect constraints
A type of dispatch. For more information consult the documentation in Effectful.Dispatch.Dynamic and Effectful.Dispatch.Static.
type family DispatchOf (e :: Effect) :: Dispatch Source #
Dispatch types of effects.
Instances
class (e :: Effect) :> (es :: [Effect]) Source #
A constraint that requires that a particular effect e
is a member of the
type-level list es
. This is used to parameterize an Eff
computation over an arbitrary list of effects, so long as e
is somewhere
in the list.
For example, a computation that only needs access to a mutable value of type
Integer
would have the following type:
State
Integer
:>
es =>Eff
es ()
Instances
(TypeError (('Text "There is no handler for '" ':<>: 'ShowType e) ':<>: 'Text "' in the context") :: Constraint) => e :> ('[] :: [Effect]) Source # | |
Defined in Effectful.Internal.Effect reifyIndex :: Int Source # | |
e :> (e ': es) Source # | |
Defined in Effectful.Internal.Effect reifyIndex :: Int Source # | |
e :> es => e :> (x ': es) Source # | |
Defined in Effectful.Internal.Effect reifyIndex :: Int Source # |
type family xs :>> es :: Constraint where ... Source #
Deprecated: Usage of (:>>) slows down GHC too much. See https://github.com/haskell-effectful/effectful/issues/52#issuecomment-1269155485 for more information.
Convenience operator for expressing that a function uses multiple effects
in a more concise way than enumerating them all with (:>)
.
[E1, E2, ..., En]:>>
es ≡ (E1:>
es, E2:>
es, ..., En :> es)
Running the Eff
monad
Pure computations
runPureEff :: HasCallStack => Eff '[] a -> a Source #
Computations with side effects
runEff :: HasCallStack => Eff '[IOE] a -> IO a Source #
Run an Eff
computation with side effects.
For running pure computations see runPureEff
.
Run arbitrary IO
computations via MonadIO
or MonadUnliftIO
.
Note: it is not recommended to use this effect in application code as it is too liberal. Ideally, this is only used in handlers of more fine-grained effects.
Instances
type DispatchOf IOE Source # | |
Defined in Effectful.Internal.Monad | |
newtype StaticRep IOE Source # | |
Defined in Effectful.Internal.Monad |
Unlifting
data UnliftStrategy Source #
The strategy to use when unlifting Eff
computations via
withEffToIO
or the localUnlift
family.
SeqUnlift | The sequential strategy is the fastest and a default setting for
|
SeqForkUnlift | Like The main consequence is that thread local state is forked at the point of creation of the unlifting function and its modifications in unlifted actions will not affect the main thread of execution (and vice versa):
Because of this it's possible to safely use the unlifting function outside
of the scope of effects it captures, e.g. by creating an
This doesn't work with the
However, it does with the
|
ConcUnlift !Persistence !Limit | The concurrent strategy makes it possible for the unlifting function to
be called in threads distinct from its creator. See |
Instances
data Persistence Source #
Persistence setting for the ConcUnlift
strategy.
Different functions require different persistence strategies. Examples:
- Lifting
pooledMapConcurrentlyN
from theunliftio
library requires theEphemeral
strategy as we don't want jobs to share environment changes made by previous jobs run in the same worker thread. - Lifting
forkIOWithUnmask
requires thePersistent
strategy, otherwise the unmasking function would start with a fresh environment each time it's called.
Ephemeral | Don't persist the environment between calls to the unlifting function in threads distinct from its creator. |
Persistent | Persist the environment between calls to the unlifting function within a particular thread. |
Instances
Generic Persistence Source # | |
Defined in Effectful.Internal.Unlift type Rep Persistence :: Type -> Type # from :: Persistence -> Rep Persistence x # to :: Rep Persistence x -> Persistence # | |
Show Persistence Source # | |
Defined in Effectful.Internal.Unlift showsPrec :: Int -> Persistence -> ShowS # show :: Persistence -> String # showList :: [Persistence] -> ShowS # | |
Eq Persistence Source # | |
Defined in Effectful.Internal.Unlift (==) :: Persistence -> Persistence -> Bool # (/=) :: Persistence -> Persistence -> Bool # | |
Ord Persistence Source # | |
Defined in Effectful.Internal.Unlift compare :: Persistence -> Persistence -> Ordering # (<) :: Persistence -> Persistence -> Bool # (<=) :: Persistence -> Persistence -> Bool # (>) :: Persistence -> Persistence -> Bool # (>=) :: Persistence -> Persistence -> Bool # max :: Persistence -> Persistence -> Persistence # min :: Persistence -> Persistence -> Persistence # | |
type Rep Persistence Source # | |
Defined in Effectful.Internal.Unlift |
Limit setting for the ConcUnlift
strategy.
Limited !Int | Behavior dependent on the For For |
Unlimited | Unlimited use of the unlifting function. |
Instances
Generic Limit Source # | |
Show Limit Source # | |
Eq Limit Source # | |
Ord Limit Source # | |
type Rep Limit Source # | |
Defined in Effectful.Internal.Unlift type Rep Limit = D1 ('MetaData "Limit" "Effectful.Internal.Unlift" "effectful-core-2.5.1.0-6xcqkxbvBS08JcWnTI2YC6" 'False) (C1 ('MetaCons "Limited" 'PrefixI 'False) (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 Int)) :+: C1 ('MetaCons "Unlimited" 'PrefixI 'False) (U1 :: Type -> Type)) |
unliftStrategy :: IOE :> es => Eff es UnliftStrategy Source #
Get the current UnliftStrategy
.
Note: this strategy is implicitly used by the MonadUnliftIO
and
MonadBaseControl
instance for Eff
.
withUnliftStrategy :: IOE :> es => UnliftStrategy -> Eff es a -> Eff es a Source #
Locally override the current UnliftStrategy
with the given value.
:: (HasCallStack, IOE :> es) | |
=> ((forall r. Eff es r -> IO r) -> IO a) | Continuation with the unlifting function in scope. |
-> Eff es a |
Create an unlifting function with the SeqUnlift
strategy. For the general
version see withEffToIO
.
Note: usage of this function is preferrable to withRunInIO
because of explicit unlifting strategy and better error reporting.
Since: 2.2.2.0
:: (HasCallStack, IOE :> es) | |
=> UnliftStrategy | |
-> ((forall r. Eff es r -> IO r) -> IO a) | Continuation with the unlifting function in scope. |
-> Eff es a |
Create an unlifting function with the given strategy.
Note: usage of this function is preferrable to withRunInIO
because of explicit unlifting strategy and better error reporting.
:: (HasCallStack, IOE :> es) | |
=> Persistence | |
-> Limit | |
-> ((forall r. Eff es r -> IO r) -> IO a) | Continuation with the unlifting function in scope. |
-> Eff es a |
Deprecated: Use withEffToIO with the appropriate strategy.
Create an unlifting function with the ConcUnlift
strategy.
Since: 2.2.2.0
Lifting
raise :: Eff es a -> Eff (e : es) a Source #
Lift an Eff
computation into an effect stack with one more effect.
:: HasCallStack | |
=> UnliftStrategy | |
-> ((forall r. Eff (e : es) r -> Eff es r) -> Eff es a) | Continuation with the unlifting function in scope. |
-> Eff (e : es) a |
Lift an Eff
computation into an effect stack with one more effect and
create an unlifting function with the given strategy.
Since: 1.2.0.0
subsume :: e :> es => Eff (e : es) a -> Eff es a Source #
Eliminate a duplicate effect from the top of the effect stack.
inject :: Subset subEs es => Eff subEs a -> Eff es a Source #
Allow for running an effect stack subEs
within es
as long as subEs
is
a permutation (with possible duplicates) of a subset of es
.
Generalizes raise
and subsume
.
>>>
data E1 :: Effect
>>>
data E2 :: Effect
>>>
data E3 :: Effect
It makes it possible to rearrange the effect stack however you like:
>>>
:{
shuffle :: Eff (E3 : E1 : E2 : es) a -> Eff (E1 : E2 : E3 : es) a shuffle = inject :}
It can also turn a monomorphic effect stack into a polymorphic one:
>>>
:{
toPoly :: (E1 :> es, E2 :> es, E3 :> es) => Eff [E1, E2, E3] a -> Eff es a toPoly = inject :}
Moreover, it allows for hiding specific effects from downstream:
>>>
:{
onlyE1 :: Eff (E1 : es) a -> Eff (E1 : E2 : E3 : es) a onlyE1 = inject :}
>>>
:{
onlyE2 :: Eff (E2 : es) a -> Eff (E1 : E2 : E3 : es) a onlyE2 = inject :}
>>>
:{
onlyE3 :: Eff (E3 : es) a -> Eff (E1 : E2 : E3 : es) a onlyE3 = inject :}
However, it's not possible to inject a computation into an incompatible effect stack:
>>>
:{
coerceEs :: Eff es1 a -> Eff es2 a coerceEs = inject :} ... ...Couldn't match type ‘es1’ with ‘es2’ ...
class KnownPrefix es => Subset (subEs :: [Effect]) (es :: [Effect]) Source #
Provide evidence that subEs
is a subset of es
.
Instances
(KnownPrefix es, IsUnknownSuffixOf subEs es) => Subset subEs es Source # | |
Defined in Effectful.Internal.Effect subsetFullyKnown :: Bool Source # reifyIndices :: [Int] Source # | |
KnownPrefix es => Subset ('[] :: [Effect]) es Source # | |
Defined in Effectful.Internal.Effect subsetFullyKnown :: Bool Source # reifyIndices :: [Int] Source # | |
(e :> es, Subset subEs es) => Subset (e ': subEs) es Source # | |
Defined in Effectful.Internal.Effect subsetFullyKnown :: Bool Source # reifyIndices :: [Int] Source # |
Re-exports
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:
Lift a computation from the IO
monad.
This allows us to run IO computations in any monadic stack, so long as it supports these kinds of operations
(i.e. IO
is the base monad for the stack).
Example
import Control.Monad.Trans.State -- from the "transformers" library printState :: Show s => StateT s IO () printState = do state <- get liftIO $ print state
Had we omitted
, we would have ended up with this error:liftIO
• Couldn't match type ‘IO’ with ‘StateT s IO’ Expected type: StateT s IO () Actual type: IO ()
The important part here is the mismatch between StateT s IO ()
and
.IO
()
Luckily, we know of a function that takes an
and returns an IO
a(m a)
:
,
enabling us to run the program and see the expected results:liftIO
> evalStateT printState "hello" "hello" > evalStateT printState 3 3
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 function run
provided by withRunInIO
, it must meet the
monad transformer laws as reformulated for MonadUnliftIO
:
run . return = return
run (m >>= f) = run m >>= run . f
Instances of MonadUnliftIO
must also satisfy the following laws:
- Identity law
withRunInIO (\run -> run m) = m
- Inverse law
withRunInIO (\_ -> m) = liftIO m
As an example of an invalid instance, a naive implementation of
MonadUnliftIO (StateT s m)
might be
withRunInIO inner = StateT $ \s -> withRunInIO $ \run -> inner (run . flip evalStateT s)
This breaks the identity law because the inner run m
would throw away
any state changes in m
.
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 # | Instance included for compatibility with existing code. Usage of Note: the unlifting strategy for |
Defined in Effectful.Internal.Monad | |
MonadUnliftIO m => MonadUnliftIO (IdentityT m) | |
Defined in Control.Monad.IO.Unlift | |
MonadUnliftIO m => MonadUnliftIO (ReaderT r m) | |
Defined in Control.Monad.IO.Unlift |