HORIZON HASKELLDocslts/ghc-9.10.xc74966e2026-09-27Search names, modules, packages, or :: a typeCtrl K

GHC 9.10.3 · lts/ghc-9.10.x · c74966e · 2026-09-27

Moduleeffectful-core-2.3.0.1Haskell2010

Effectful.Dispatch.Static

Statically dispatched effects.

  • 2 types
  • 16 values

Introduction

0 declarations

Unlike dynamically dispatched effects, statically dispatched effects have a single, set interpretation that cannot be changed at runtime, which makes them useful in specific scenarios. For example:

  • If you'd like to ensure that a specific effect will behave in a certain way at all times, using a statically dispatched version is the only way to ensure that.

  • If the effect you're about to define has only one reasonable implementation, it makes a lot of sense to make it statically dispatched.

Statically dispatched effects also perform slightly better than dynamically dispatched ones, because their operations are implemented as standard top level functions, so the compiler can apply more optimizations to them.

An example

Let's say that there exists a logging library whose functionality we'd like to turn into an effect. Its Logger data type (after simplification) is represented in the following way:

Example1 expression
data Logger = Logger { logMessage :: String -> IO () }

Because the Logger type itself allows customization of how messages are logged, it is an excellent candidate to be turned into a statically dispatched effect.

Such effect is represented by an empty data type of kind Effect:

Example1 expression
data Log :: Effect

When it comes to the dispatch, we also need to signify whether core operations of the effect will perform side effects. Since GHC is not a polygraph, you can lie, though being truthful is recommended 🙂

Example1 expression
type instance DispatchOf Log = Static WithSideEffects

The environment of Eff will hold the data type that represents the effect. It is defined by the appropriate instance of the StaticRep data family:

Example1 expression
newtype instance StaticRep Log = Log Logger

Note: all operations of a statically dispatched effect will have a read/write access to this data type as long as they can see its constructors, hence it's best not to export them from the module that defines the effect.

The logging operation can be defined as follows:

Example1 expression
:{ log :: (IOE :> es, Log :> es) => String -> Eff es () log msg = do   Log logger <- getStaticRep   liftIO $ logMessage logger msg:}

That works, but has an unfortunate consequence: in order to use the log operation the IOE effect needs to be in scope! This is bad, because we're trying to limit (ideally, fully eliminate) the need to have the full power of IO available in the application code. The solution is to use one of the escape hatches that allow unrestricted access to the internal representation of Eff:

Example1 expression
:{ log :: Log :> es => String -> Eff es () log msg = do   Log logger <- getStaticRep   unsafeEff_ $ logMessage logger msg:}

However, since logging is most often an operation with side effects, in order for this approach to be sound, the function that introduces the Log effect needs to require the IOE effect.

If you forget to do that, don't worry. As long as the DispatchOf instance was correctly defined to be Static WithSideEffects, you will get a reminder:

Example1 expression
:{ runLog :: Logger -> Eff (Log : es) a -> Eff es a runLog logger = evalStaticRep (Log logger):}......No instance for ...IOE :> es... arising from a use of ‘evalStaticRep’...

Including IOE :> es in the context fixes the problem:

Example1 expression
:{ runLog :: IOE :> es => Logger -> Eff (Log : es) a -> Eff es a runLog logger = evalStaticRep (Log logger):}

In general, whenever any operation of a statically dispatched effect performs side effects using one of the unsafe functions, all functions that introduce this effect need to require the IOE effect (otherwise it would be possible to run it via runPureEff).

Now we can use the newly defined effect to log messages:

Example1 expression
dummyLogger = Logger { logMessage = \_ -> pure () }
Example1 expression
stdoutLogger = Logger { logMessage = putStrLn }
Example1 expression
:{  action = do    log "Computing things..."    log "Sleeping..."    log "Computing more things..."    pure True:}
Example1 expression
:t actionaction :: (Log :> es) => Eff es Bool
Example1 expression
runEff . runLog stdoutLogger $ actionComputing things...Sleeping...Computing more things...True
Example1 expression
runEff . runLog dummyLogger $ actionTrue

Low level API

3 declarations
data familydata family StaticRep (e :: Effect)
#

Internal representations of statically dispatched effects.

Instances10StaticRep, …
  • data StaticRep IOEDefined in effectful-core-2.3.0.1 · Effectful.Internal.Monad
  • data StaticRep Prim
    • Prim
    Defined in effectful-core-2.3.0.1 · Effectful.Internal.Monad
  • data StaticRep (Error e)
    • Error ErrorId
    Defined in effectful-core-2.3.0.1 · Effectful.Error.Static
  • data StaticRep (Labeled label e)Defined in effectful-core-2.3.0.1 · Effectful.Labeled
  • data StaticRep (Provider e input f)
    • Provider :: !Env handlerEs -> !(forall r. input -> Eff (e ': handlerEs) r -> Eff handlerEs (f r)) -> R:StaticRepProvider e input f
    Defined in effectful-core-2.3.0.1 · Effectful.Provider
  • data StaticRep (Reader r)
    • Reader r
    Defined in effectful-core-2.3.0.1 · Effectful.Reader.Static
  • data StaticRep (State s)
    • State s
    Defined in effectful-core-2.3.0.1 · Effectful.State.Static.Local
  • data StaticRep (State s)Defined in effectful-core-2.3.0.1 · Effectful.State.Static.Shared
  • data StaticRep (Writer w)
    • Writer w
    Defined in effectful-core-2.3.0.1 · Effectful.Writer.Static.Local
  • data StaticRep (Writer w)Defined in effectful-core-2.3.0.1 · Effectful.Writer.Static.Shared

Extending the environment

valueevalStaticRep
  1. :: (DispatchOf e ~ 'Static sideEffects, MaybeIOE sideEffects es)
  2. => StaticRep e

    The initial representation.

  3. -> Eff (e ': es) a
  4. -> Eff es a
#

Run a statically dispatched effect with the given initial representation and return the final value, discarding the final representation.

Data retrieval and update

Unlifts

valueunsafeSeqUnliftIO
  1. :: HasCallStack
  2. => ((forall r. Eff es r -> IO r) -> IO a)

    Continuation with the unlifting function in scope.

  3. -> Eff es a
#

Create an unlifting function with the SeqUnlift strategy.

This function is unsafe because it can be used to introduce arbitrary IO actions into pure Eff computations.

Utils

valueunsafeEff :: (Env es -> IO a) -> Eff es a
#

Access the underlying IO monad along with the environment.

This function is unsafe because it can be used to introduce arbitrary IO actions into pure Eff computations.

valueunsafeEff_ :: IO a -> Eff es a
#

Access the underlying IO monad.

This function is unsafe because it can be used to introduce arbitrary IO actions into pure Eff computations.

valueunsafeLiftMapIO :: HasCallStack => (IO a -> IO b) -> Eff es a -> Eff es b
#

Utility for lifting IO computations of type

IO a -> IO b

to

Eff es a -> Eff es b

Note: the computation must not run its argument in a separate thread, attempting to do so will result in a runtime error.

This function is unsafe because it can be used to introduce arbitrary IO actions into pure Eff computations.

Re-exports

1 declaration
typetype HasCallStack = IP "callStack" CallStack
#

Request a CallStack.

NOTE: The implicit parameter ?callStack :: CallStack is an implementation detail and should not be considered part of the CallStack API, we may decide to change the implementation in the future.