The Sem monad handles computations of arbitrary extensible effects.
A value of type Sem r describes a program with the capabilities of
r. For best results, r should always be kept polymorphic, but you can
add capabilities via the Member constraint.
The value of the Sem monad is that it allows you to write programs against a set of effects without a predefined meaning, and provide that meaning later. For example, unlike with mtl, you can decide to interpret an Error effect traditionally as an Either, or instead as (a significantly faster) IO Exception. These interpretations (and others that you might add) may be used interchangeably without needing to write any newtypes or Monad instances. The only change needed to swap interpretations is to change a call from runError to errorToIOFinal.
The effect stack r can contain arbitrary other monads inside of it. These
monads are lifted into effects via the Embed effect. Monadic values can be
lifted into a Sem via embed.
Higher-order actions of another monad can be lifted into higher-order actions
of Sem via the Polysemy.Final effect, which is more powerful
than Embed, but also less flexible to interpret.
A Sem can be interpreted as a pure value (via run) or as any
traditional Monad (via runM or Polysemy.runFinal).
Each effect E comes equipped with some interpreters of the form:
runE :: Sem (E ': r) a -> Sem r a
which is responsible for removing the effect E from the effect stack. It
is the order in which you call the interpreters that determines the
monomorphic representation of the r parameter.
Order of interpreters can be important - it determines behaviour of effects that manipulate state or change control flow. For example, when interpreting this action:
:{ example :: Members '[State String, Error String] r => Sem r String example = do put "start" let throwing, catching :: Members '[State String, Error String] r => Sem r String throwing = do modify (++"-throw") throw "error" get catching = do modify (++"-catch") get catch @String throwing (\ _ -> catching):}
when handling Error first, state is preserved after error occurs:
:{ example & runError & fmap (either id id) & evalState "" & runM & (print =<<):}"start-throw-catch"
while handling State first discards state in such cases:
:{ example & evalState "" & runError & fmap (either id id) & runM & (print =<<):}"start-catch"
A good rule of thumb is to handle effects which should have "global" behaviour over other effects later in the chain.
After all of your effects are handled, you'll be left with either
a Sem '[] a, a Sem '[ Embed m ] a, or a Sem '[
value, which can be consumed respectively by run, runM, and
Polysemy.Final m ] aPolysemy.runFinal.
Examples
As an example of keeping r polymorphic, we can consider the type
Member (State String) r => Sem r ()
to be a program with access to
get :: Sem r String
put :: String -> Sem r ()
methods.
By also adding a
Member (Polysemy.Error Bool) r
constraint on r, we gain access to the
throw :: Bool -> Sem r a
catch :: Sem r a -> (Bool -> Sem r a) -> Sem r a
functions as well.
In this sense, a Member (State s) r constraint is
analogous to mtl's MonadState s m and should
be thought of as such. However, unlike mtl, a Sem monad may have
an arbitrary number of the same effect.
For example, we can write a Sem program which can output either Ints or Bools:
foo :: ( Member (Output Int) r
, Member (Output Bool) r
)
=> Sem r ()
foo = do
output @Int 5
output True
Notice that we must use -XTypeApplications to specify that we'd like to
use the (Output Int) effect.
Instances10Monad, Functor, MonadFix, MonadFail, Applicative, Alternative, …
Monad (Sem f)Defined in polysemy-1.9.2.0 · Polysemy.InternalFunctor (Sem f)Defined in polysemy-1.9.2.0 · Polysemy.InternalMember Fixpoint r => MonadFix (Sem r)Defined in polysemy-1.9.2.0 · Polysemy.InternalMember Fail r => MonadFail (Sem r)Defined in polysemy-1.9.2.0 · Polysemy.InternalApplicative (Sem f)Defined in polysemy-1.9.2.0 · Polysemy.InternalMember NonDet r => Alternative (Sem r)Defined in polysemy-1.9.2.0 · Polysemy.InternalMember NonDet r => MonadPlus (Sem r)Defined in polysemy-1.9.2.0 · Polysemy.InternalMember (Embed IO) r => MonadIO (Sem r)Defined in polysemy-1.9.2.0 · Polysemy.InternalThis instance will only lift IO actions. If you want to lift into some other MonadIO type, use this instance, and handle it via the embedToMonadIO interpretation.
Semigroup a => Semigroup (Sem f a)Defined in polysemy-1.9.2.0 · Polysemy.InternalMonoid a => Monoid (Sem f a)Defined in polysemy-1.9.2.0 · Polysemy.Internal