HORIZON HASKELLDocslts/ghc-9.10.x248f8f02026-10-05Search names, modules, packages, or :: a typeCtrl K

GHC 9.10.3 · lts/ghc-9.10.x · 248f8f0 · 2026-10-05

Modulepolysemy-1.9.2.0Haskell2010

Polysemy.Internal

  • 4 types
  • 3 classes
  • 20 values
  • Packagepolysemy-1.9.2.0
  • Exports29
  • LanguageHaskell2010
  • LicenceBSD-3-Clause
  • SourceInternal.hs
newtypenewtype Sem (r :: EffectRow) a
#

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:

Example1 expression
:{  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:

Example1 expression
:{  example    & runError    & fmap (either id id)    & evalState ""    & runM    & (print =<<):}"start-throw-catch"

while handling State first discards state in such cases:

Example1 expression
:{  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 '[ Polysemy.Final m ] a value, which can be consumed respectively by run, runM, and Polysemy.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.

Constructors

Instances10Monad, Functor, MonadFix, MonadFail, Applicative, Alternative, …
classclass Member (t :: Effect) (r :: EffectRow) where
#

This class indicates that an effect must be present in the caller's stack. It is the main mechanism by which a program defines its effect dependencies.

Instances2Member
  • Member t (t ': z)Defined in polysemy-1.9.2.0 · Polysemy.Internal.Union
  • Member t z => Member t (_1 ': z)Defined in polysemy-1.9.2.0 · Polysemy.Internal.Union
valuesend :: Member e r => e (Sem r) a -> Sem r a
#

Execute an action of an effect.

This is primarily used to create methods for actions of effects:

data FooBar m a where
  Foo :: String -> m a -> FooBar m a
  Bar :: FooBar m Int

foo :: Member FooBar r => String -> Sem r a -> Sem r a
foo s m = send (Foo s m)

bar :: Member FooBar r => Sem r Int
bar = send Bar

makeSem allows you to eliminate this boilerplate.

@since TODO

valuesendUsing :: ElemOf e r -> e (Sem r) a -> Sem r a
#

Embed an effect into a Sem, given an explicit proof that the effect exists in r.

This is useful in conjunction with tryMembership, in order to conditionally make use of effects.

valuerun :: Sem '[] a -> a
#

Run a Sem containing no effects as a pure value.

valueraise_ :: Raise r r' => Sem r a -> Sem r' a
#

Introduce an arbitrary number of effects on top of the effect stack. This function is highly polymorphic, so it may be good idea to use its more concrete versions (like raise) or type annotations to avoid vague errors in ambiguous contexts.

valueraise :: Sem r a -> Sem (e ': r) a
#

Introduce an effect into Sem. Analogous to Control.Monad.Class.Trans.lift in the mtl ecosystem. For a variant that can introduce an arbitrary number of effects, see raise_.

valueraiseUnder :: Sem (e1 ': r) a -> Sem (e1 ': e2 ': r) a
#

Like raise, but introduces a new effect underneath the head of the list. See raiseUnder2 or raiseUnder3 for introducing more effects. If you need to introduce even more of them, check out subsume_.

raiseUnder can be used in order to turn transformative interpreters into reinterpreters. This is especially useful if you're writing an interpreter which introduces an intermediary effect, and then want to use an existing interpreter on that effect.

For example, given:

fooToBar :: Member Bar r => Sem (Foo ': r) a -> Sem r a
runBar   :: Sem (Bar ': r) a -> Sem r a

You can write:

runFoo :: Sem (Foo ': r) a -> Sem r a
runFoo =
    runBar     -- Consume Bar
  . fooToBar   -- Interpret Foo in terms of the new Bar
  . raiseUnder -- Introduces Bar under Foo
valueraiseUnder2 :: Sem (e1 ': r) a -> Sem (e1 ': e2 ': e3 ': r) a
#

Like raise, but introduces two new effects underneath the head of the list.

valueraiseUnder3 :: Sem (e1 ': r) a -> Sem (e1 ': e2 ': e3 ': e4 ': r) a
#

Like raise, but introduces three new effects underneath the head of the list.

valueraise2Under :: Sem (e1 ': e2 ': r) a -> Sem (e1 ': e2 ': e3 ': r) a
#

Like raise, but introduces an effect two levels underneath the head of the list.

valueraise3Under
  1. :: Sem (e1 ': e2 ': e3 ': r) a
  2. -> Sem (e1 ': e2 ': e3 ': e4 ': r) a
#

Like raise, but introduces an effect three levels underneath the head of the list.

valuesubsume_ :: Subsume r r' => Sem r a -> Sem r' a
#

Allows reordering and adding known effects on top of the effect stack, as long as the polymorphic "tail" of new stack is a raise-d version of the original one. This function is highly polymorphic, so it may be a good idea to use its more concrete version (subsume), fitting functions from the raise family or type annotations to avoid vague errors in ambiguous contexts.

valuesubsume :: Member e r => Sem (e ': r) a -> Sem r a
#

Interprets an effect in terms of another identical effect.

This is useful for defining interpreters that use reinterpretH without immediately consuming the newly introduced effect. Using such an interpreter recursively may result in duplicate effects, which may then be eliminated using subsume.

For a version that can introduce an arbitrary number of new effects and reorder existing ones, see subsume_.

valueinsertAt
  1. :: (ListOfLength index head, WhenStuck index InsertAtUnprovidedIndex, old ~ Append head oldTail, tail ~ Append inserted oldTail, full ~ Append head tail, InsertAtIndex index head tail oldTail full inserted)
  2. => Sem old a
  3. -> Sem full a
#

Introduce a set of effects into Sem at the index i, before the effect that previously occupied that position. This is intended to be used with a type application:

let
  sem1 :: Sem [e1, e2, e3, e4, e5] a
  sem1 = insertAt @2 (sem0 :: Sem [e1, e2, e5] a)
newtypenewtype Embed (m :: Type -> Type) (z :: Type -> Type) a where
#

An effect which allows a regular Monad m into the Sem ecosystem. Monadic actions in m can be lifted into Sem via embed.

For example, you can use this effect to lift IO actions directly into Sem:

embed (putStrLn "hello") :: Member (Embed IO) r => Sem r ()

That being said, you lose out on a significant amount of the benefits of Sem by using embed directly in application code; doing so will tie your application code directly to the underlying monad, and prevent you from interpreting it differently. For best results, only use Embed in your effect interpreters.

Consider using trace and traceToIO as a substitute for using putStrLn directly.

Constructors

familytype family Append (l :: [a]) (r :: [a]) :: [a] where
#

Append two type-level lists.

Equations

typetype InterpreterFor (e :: Effect) (r :: [Effect]) = forall a. Sem (e ': r) a -> Sem r a
#

Type synonym for interpreters that consume an effect without changing the return value. Offered for user convenience.

r Is kept polymorphic so it's possible to place constraints upon it:

teletypeToIO :: Member (Embed IO) r
             => InterpreterFor Teletype r

Orphan instances

1 instance
  • PluginLookup Plugin

    Due to a quirk of the GHC plugin interface, it's only easy to find transitive dependencies if they define an orphan instance. This orphan instance allows us to find Polysemy.Internal in the polysemy-plugin.