send :: (HasCallStack, DispatchOf e ~ 'Dynamic, e :> es)=> e (Eff es) aThe operation.
-> Eff es a
Send an operation of the given effect to its handler for execution.
:: a typeCtrl KGHC 9.14.1 · lts/ghc-9.14.x · 45dd537 · 2026-10-11
Moduleeffectful-core-2.7.1.2GHC2021
Dynamically dispatched effects.
A dynamically dispatched effect is a collection of operations that can be interpreted in different ways at runtime, depending on the handler that is used to run the effect.
This allows a programmer to separate the what from the how, i.e. define effects that model what the code should do, while providing handlers that determine how it should do it later. Moreover, different environments can use different handlers to change the behavior of specific parts of the application if appropriate.
Let's create an effect for basic file access, i.e. writing and reading files.
First, we need to define a generalized algebraic data type of kind Effect, where each constructor corresponds to a specific operation of the effect in question.
:{ data FileSystem :: Effect where ReadFile :: FilePath -> FileSystem m String WriteFile :: FilePath -> String -> FileSystem m ():}
type instance DispatchOf FileSystem = DynamicThe FileSystem effect has two operations:
ReadFile, which takes a FilePath and returns a String in the monadic
context.
WriteFile, which takes a FilePath, a String and returns a () in the
monadic context.
For people familiar with mtl style effects, note that the syntax looks very
similar to defining an appropriate type class:
class FileSystem m where
readFile :: FilePath -> m String
writeFile :: FilePath -> String -> m ()
The biggest difference between these two is that the definition of a type class gives us operations as functions, while the definition of an effect gives us operations as data constructors. They can be turned into functions with the help of send:
:{ readFile :: (HasCallStack, FileSystem :> es) => FilePath -> Eff es String readFile path = send (ReadFile path):}
:{ writeFile :: (HasCallStack, FileSystem :> es) => FilePath -> String -> Eff es () writeFile path content = send (WriteFile path content):}
Note: the above functions and the DispatchOf instance can also be
automatically generated by the
makeEffect
function from the
effectful-th package.
The following defines an EffectHandler that reads and writes files from the drive:
import Effectful.Error.Staticimport Effectful.Exceptionimport System.IO qualified as IO
newtype FsError = FsError String deriving Show:{ runFileSystemIO :: (IOE :> es, Error FsError :> es) => Eff (FileSystem : es) a -> Eff es a runFileSystemIO = interpret $ \_ -> \case ReadFile path -> adapt $ IO.readFile path WriteFile path contents -> adapt $ IO.writeFile path contents where adapt m = liftIO m `catchIO` \e -> throwError . FsError $ show e:}
Here, we use interpret and simply execute corresponding IO actions for each operation, additionally doing a bit of error management.
On the other hand, maybe there is a situation in which instead of interacting with the outside world, a pure, in-memory storage is preferred:
import Data.Map.Strict qualified as Mimport Effectful.State.Static.Local
:{ runFileSystemPure :: Error FsError :> es => M.Map FilePath String -> Eff (FileSystem : es) a -> Eff es a runFileSystemPure fs0 = reinterpret (evalState fs0) $ \_ -> \case ReadFile path -> gets (M.lookup path) >>= \case Just contents -> pure contents Nothing -> throwError . FsError $ "File not found: " ++ show path WriteFile path contents -> modify $ M.insert path contents:}
Here, we use reinterpret and introduce a State effect for the storage that is private to the effect handler and cannot be accessed outside of it.
Let's compare how these differ.
:{ action = do file <- readFile "effectful-core.cabal" pure $ length file > 0:}
:t actionaction :: (FileSystem :> es) => Eff es Bool
runEff . runError @FsError . runFileSystemIO $ actionRight True
runPureEff . runErrorNoCallStack @FsError . runFileSystemPure M.empty $ actionLeft (FsError "File not found: \"effectful-core.cabal\"")
Note that the definition of the FileSystem effect from the previous section
doesn't use the m type parameter. What is more, when the effect is
interpreted, the LocalEnv argument of the EffectHandler is also not
used. Such effects are first order.
If an effect makes use of the m parameter, it is a higher order effect.
Note: for handling first order effects you can use interpret_ or reinterpret_ whose EffectHandler_ doesn't take the LocalEnv parameter.
Interpretation of higher order effects is slightly more involving. To see
why, let's consider the Profiling effect for logging how much time a
specific action took to run:
:{ data Profiling :: Effect where Profile :: String -> m a -> Profiling m a:}
type instance DispatchOf Profiling = Dynamic:{ profile :: (HasCallStack, Profiling :> es) => String -> Eff es a -> Eff es a profile label action = send (Profile label action):}
If we naively try to interpret it, we will run into trouble:
import GHC.Clock (getMonotonicTime):{ runProfiling :: IOE :> es => Eff (Profiling : es) a -> Eff es a runProfiling = interpret $ \_ -> \case Profile label action -> do t1 <- liftIO getMonotonicTime r <- action t2 <- liftIO getMonotonicTime liftIO . putStrLn $ "Action '" ++ label ++ "' took " ++ show (t2 - t1) ++ " seconds." pure r:}...... Couldn't match type ‘localEs’ with ‘es’...
The problem is that action has a type Eff localEs a, while the monad of
the effect handler is Eff es. localEs represents the local environment
in which the Profile operation was called, which is more or less opaque to
the effect handler.
The solution is to use the LocalEnv that an EffectHandler is given to run the action using one of the functions from the localUnlift family:
:{ runProfiling :: IOE :> es => Eff (Profiling : es) a -> Eff es a runProfiling = interpret $ \env -> \case Profile label action -> localSeqUnliftIO env $ \unlift -> do t1 <- getMonotonicTime r <- unlift action t2 <- getMonotonicTime putStrLn $ "Action '" ++ label ++ "' took " ++ show (t2 - t1) ++ " seconds." pure r:}
In a similar way we can define a dummy interpreter that does no profiling:
:{ runNoProfiling :: Eff (Profiling : es) a -> Eff es a runNoProfiling = interpret $ \env -> \case Profile label action -> localSeqUnlift env $ \unlift -> unlift action:}
...and it's done.
action = profile "greet" . liftIO $ putStrLn "Hello!":t actionaction :: (Profiling :> es, IOE :> es) => Eff es ()
runEff . runProfiling $ actionHello!Action 'greet' took ... seconds.
runEff . runNoProfiling $ actionHello!
mtl style effectsThere exists a lot of libraries that provide their functionality as an mtl
style effect, which generally speaking is a type class that contains core
operations of the library in question.
Such effects are quite easy to use with the Eff monad. As an example,
consider the mtl style effect for generation of random numbers:
:{ class Monad m => MonadRNG m where randomInt :: m Int:}
Let's say the library also defines a helper function for generation of random strings:
import Control.Monadimport Data.Char
:{ randomString :: MonadRNG m => Int -> m String randomString n = map chr <$> replicateM n randomInt:}
To make it possible to use it with the Eff monad, the first step is to create an effect with operations that mirror the ones of a type class:
:{ data RNG :: Effect where RandomInt :: RNG m Int:}
type instance DispatchOf RNG = DynamicIf we continued as in the example above, we'd now create top level helper
functions that execute effect operations using send, in this case
randomInt tied to RandomInt. But this function is already declared by the
MonadRNG type class! Therefore, what we do instead is provide an
orphan, canonical instance of MonadRNG for Eff that delegates to
the RNG effect:
:{ instance RNG :> es => MonadRNG (Eff es) where randomInt = send RandomInt:}
Now we only need an interpreter:
:{ runDummyRNG :: Eff (RNG : es) a -> Eff es a runDummyRNG = interpret_ $ \case RandomInt -> pure 55:}
and we can use any function that requires a MonadRNG constraint with the
Eff monad as long as the RNG effect is in place:
runEff . runDummyRNG $ randomString 3"777"
For dealing with classes that employ functional dependencies an additional trick is needed.
Consider the following:
:set -XFunctionalDependencies:{ class Monad m => MonadInput i m | m -> i where input :: m i:}
An attempt to define the instance as in the example above leads to violation of the liberal coverage condition:
:{ instance Reader i :> es => MonadInput i (Eff es) where input = ask:}......Illegal instance declaration for ‘MonadInput i (Eff es)’...... The liberal coverage condition fails in class ‘MonadInput’...... for functional dependency: ‘m -> i’......
However, there exists a trick for bypassing the coverage condition, i.e. including the instance head in its context:
:{ instance (MonadInput i (Eff es), Reader i :> es) => MonadInput i (Eff es) where input = ask:}
Now the MonadInput class can be used with the Eff monad:
:{ double :: MonadInput Int m => m Int double = (+) <$> input <*> input:}
runPureEff . runReader @Int 3 $ double6
send :: (HasCallStack, DispatchOf e ~ 'Dynamic, e :> es)=> e (Eff es) aThe operation.
-> Eff es aSend an operation of the given effect to its handler for execution.
passthrough :: (HasCallStack, DispatchOf e ~ 'Dynamic, e :> es, e :> localEs)=> LocalEnv localEs-> e (Eff localEs) aThe operation.
-> Eff es atype EffectHandler (e :: Effect) (es :: [Effect]) = forall a (localEs :: [Effect]). (HasCallStack, e :> localEs) => LocalEnv localEs -> e (Eff localEs) a -> Eff es aType signature of the effect handler.
interpret :: (HasCallStack, DispatchOf e ~ 'Dynamic)=> EffectHandler e esThe effect handler.
-> Eff (e ': es) a-> Eff es aInterpret an effect.
Note: if you want to use intermediate effects in multiple handlers, then hide them from downstream, have a look at inject.
interpretWith :: (HasCallStack, DispatchOf e ~ 'Dynamic)=> Eff (e ': es) a-> EffectHandler e esThe effect handler.
-> Eff es ainterpret with the effect handler as the last argument.
reinterpret :: (HasCallStack, DispatchOf e ~ 'Dynamic)=> (Eff handlerEs a -> Eff es b)Setup of effects encapsulated in the handler.
-> EffectHandler e handlerEsThe effect handler.
-> Eff (e ': es) a-> Eff es bInterpret an effect using other, private effects.
interpret ≡ reinterpret idNote: If you want to interpret multiple effects using other, private effects, you can do so with a combination of interpret and inject.
This is in particular useful for splitting a large effect into smaller ones. For example, let's say you want to split a State into a read only and read write component:
:{ data Get s :: Effect where Get :: Get s m s type instance DispatchOf (Get s) = Dynamic:}
:{ data Put s :: Effect where Put :: s -> Put s m () type instance DispatchOf (Put s) = Dynamic:}
import Effectful.State.Static.Local qualified as S:{ runGetPut :: forall s es a. s -> Eff (Get s : Put s : es) a -> Eff es (a, s) runGetPut s0 = S.runState s0 . interpret_ @(Put s) (\(Put s) -> S.put s) . interpret_ @(Get s) (\Get -> S.get) . inject:}
Here, a State effect is introduced, then
Put and Get effects that use it underneath and finally inject hides the
original state from downstream code.
reinterpretWith :: (HasCallStack, DispatchOf e ~ 'Dynamic)=> (Eff handlerEs a -> Eff es b)Setup of effects encapsulated in the handler.
-> Eff (e ': es) a-> EffectHandler e handlerEsThe effect handler.
-> Eff es breinterpret with the effect handler as the last argument.
interpose :: (HasCallStack, DispatchOf e ~ 'Dynamic, e :> es)=> EffectHandler e esThe effect handler.
-> Eff es a-> Eff es aReplace the handler of an existing effect with a new one.
Note: this function allows for augmenting handlers with a new functionality as the new handler can send operations to the old one.
:{ data E :: Effect where Op1 :: E m () Op2 :: E m () Op3 :: E m () type instance DispatchOf E = Dynamic:}
:{ runE :: IOE :> es => Eff (E : es) a -> Eff es a runE = interpret_ $ \case Op1 -> liftIO (putStrLn "op1") Op2 -> liftIO (putStrLn "op2") Op3 -> error "Op3 not implemented":}
let action = send Op1 >> send Op2runEff . runE $ actionop1op2
You can modify only specific operations and send the rest to the upstream handler with passthrough:
:{ augmentOp2 :: (E :> es, IOE :> es) => Eff es a -> Eff es a augmentOp2 = interpose $ \env -> \case Op2 -> liftIO (putStrLn "augmented op2") >> send Op2 op -> passthrough env op:}
runEff . runE . augmentOp2 $ actionop1augmented op2op2
Note: when an exception is raised while handling an operation, good debugging experience is ensured by strategically placed HasCallStack constraints:
runEff . runE . augmentOp2 $ send Op3*** Exception: Op3 not implemented... error, called at <interactive>:... handler, called at src/Effectful/Dispatch/Dynamic.hs:... passthrough, called at <interactive>:... handler, called at src/Effectful/Dispatch/Dynamic.hs:... send, called at <interactive>:......
interposeWith :: (HasCallStack, DispatchOf e ~ 'Dynamic, e :> es)=> Eff es a-> EffectHandler e esThe effect handler.
-> Eff es ainterpose with the effect handler as the last argument.
impose :: (HasCallStack, DispatchOf e ~ 'Dynamic, e :> es)=> (Eff handlerEs a -> Eff es b)Setup of effects encapsulated in the handler.
-> EffectHandler e handlerEsThe effect handler.
-> Eff es a-> Eff es bimposeWith :: (HasCallStack, DispatchOf e ~ 'Dynamic, e :> es)=> (Eff handlerEs a -> Eff es b)Setup of effects encapsulated in the handler.
-> Eff es a-> EffectHandler e handlerEsThe effect handler.
-> Eff es bimpose with the effect handler as the last argument.
localSeqUnlift Create a local unlifting function with the SeqUnlift strategy. For the general version see localUnlift.
localSeqUnliftIO Create a local unlifting function with the SeqUnlift strategy. For the general version see localUnliftIO.
localUnlift :: HasCallStack=> LocalEnv localEsLocal environment.
-> UnliftStrategy-> ((forall r. Eff localEs r -> Eff es r) -> Eff es a)Continuation with the unlifting function in scope.
-> Eff es aCreate a local unlifting function with the given strategy.
localUnliftIO :: (HasCallStack, IOE :> es)=> LocalEnv localEsLocal environment.
-> UnliftStrategy-> ((forall r. Eff localEs r -> IO r) -> IO a)Continuation with the unlifting function in scope.
-> Eff es aCreate a local unlifting function with the given strategy.
localSeqLift localLift :: HasCallStack=> LocalEnv localEsLocal environment.
-> UnliftStrategy-> ((forall r. Eff es r -> Eff localEs r) -> Eff es a)Continuation with the lifting function in scope.
-> Eff es aCreate a local lifting function with the given strategy.
withLiftMap Deprecated. Misusing withLiftMap in multiple threads results in undefined behavior. Use localLiftUnlift with an appropriate UnliftStrategy instead.
Utility for lifting Eff computations of type
Eff es a -> Eff es bto
Eff localEs a -> Eff localEs bNote: the computation must not run its argument in a different thread, attempting to do so will result in a runtime error.
Warning: if the lifting function is used in a thread distinct from its creator, the lifted computation must not interact with the environment. This cannot be detected at runtime, hence the deprecation.
withLiftMapIO Utility for lifting IO computations of type
IO a -> IO bto
Eff localEs a -> Eff localEs bNote: the computation must not run its argument in a different thread, attempting to do so will result in a runtime error.
Useful e.g. for lifting the unmasking function in mask-like computations:
:{data Fork :: Effect where ForkWithUnmask :: ((forall a. m a -> m a) -> m ()) -> Fork m ThreadIdtype instance DispatchOf Fork = Dynamic:}
:{runFork :: IOE :> es => Eff (Fork : es) a -> Eff es arunFork = interpret $ \env (ForkWithUnmask m) -> withLiftMapIO env $ \liftMap -> do localUnliftIO env (ConcUnlift Ephemeral $ Limited 1) $ \unlift -> do forkIOWithUnmask $ \unmask -> unlift $ m $ liftMap unmask:}
localLiftUnlift :: HasCallStack=> LocalEnv localEsLocal environment.
-> UnliftStrategy-> ((forall r. Eff es r -> Eff localEs r) -> (forall r. Eff localEs r -> Eff es r) -> Eff es a)Continuation with the lifting and unlifting function in scope.
-> Eff es aCreate a local lifting and unlifting function with the given strategy.
Useful for lifting complicated Eff computations where the monadic action shows in both positive (as a result) and negative (as an argument) position.
Note: when SeqForkUnlift or ConcUnlift Persistent strategy is used, the unlifting functions will share the effect storage (unlike with two separate calls to localLift and localUnlift).
localLiftUnliftIO Create a local unlifting function with the given strategy along with an unrestricted lifting function.
Useful for lifting complicated IO computations where the monadic action shows in both positive (as a result) and negative (as an argument) position.
Note: depending on the computation you're lifting localUnliftIO along with withLiftMapIO might be enough and is more efficient.
localSeqLend :: (HasCallStack, KnownSubset lentEs es)=> LocalEnv localEs-> ((forall r. Eff (lentEs ++ localEs) r -> Eff localEs r) -> Eff es a)Continuation with the lent handler in scope.
-> Eff es aLend effects to the local environment.
Consider the following effect:
:{ data D :: Effect where D :: D m () type instance DispatchOf D = Dynamic:}
and an auxiliary effect that requires both IOE and D to run:
:{ data E :: Effect runE :: (IOE :> es, D :> es) => Eff (E : es) a -> Eff es a runE = error "runE":}
Trying to use runE inside the handler of D doesn't work out of the box:
:{ runD :: IOE :> es => Eff (D : es) a -> Eff es a runD = interpret $ \env -> \case D -> localSeqUnlift env $ \unlift -> do unlift . runE $ pure ():}......Could not deduce ...IOE :> localEs... arising from a use of ‘runE’...from the context: IOE :> es...
The problem is that runE needs IOE :> localEs, but only IOE :> es is
available. This function allows us to bridge the gap:
:{ runD :: IOE :> es => Eff (D : es) a -> Eff es a runD = interpret $ \env -> \case D -> localSeqUnlift env $ \unlift -> do localSeqLend @'[IOE] env $ \useIOE -> do unlift . useIOE . runE $ pure ():}
localLend :: (HasCallStack, KnownSubset lentEs es)=> LocalEnv localEs-> UnliftStrategy-> ((forall r. Eff (lentEs ++ localEs) r -> Eff localEs r) -> Eff es a)Continuation with the lent handler in scope.
-> Eff es aLend effects to the local environment with a given unlifting strategy.
Generalizes localSeqLend.
localSeqBorrow :: (HasCallStack, KnownSubset borrowedEs localEs)=> LocalEnv localEs-> ((forall r. Eff (borrowedEs ++ es) r -> Eff es r) -> Eff es a)Continuation with the borrowed handler in scope.
-> Eff es aBorrow effects from the local environment.
localBorrow :: (HasCallStack, KnownSubset borrowedEs localEs)=> LocalEnv localEs-> UnliftStrategy-> ((forall r. Eff (borrowedEs ++ es) r -> Eff es r) -> Eff es a)Continuation with the borrowed handler in scope.
-> Eff es aBorrow effects from the local environment with a given unlifting strategy.
Generalizes localSeqBorrow.
localLendBorrow :: (HasCallStack, KnownSubset lentEs es, KnownSubset borrowedEs localEs)=> LocalEnv localEs-> UnliftStrategy-> ((forall r. Eff (lentEs ++ localEs) r -> Eff localEs r) -> (forall r. Eff (borrowedEs ++ es) r -> Eff es r) -> Eff es a)Continuation with the lending and borrowing functions in scope.
-> Eff es aSimultaneously lend effects to the local environment and borrow effects from it with a given unlifting strategy.
Note: when SeqForkUnlift or ConcUnlift Persistent strategy is used, the lending and borrowing functions will share the effect storage (unlike with two separate calls to localLend and localBorrow).
Provide evidence that subEs is a known subset of es.
KnownSubset '[] esDefined in effectful-core-2.7.1.2 · Effectful.Internal.Effect(e :> es, KnownSubset subEs es) => KnownSubset (e ': subEs) esDefined in effectful-core-2.7.1.2 · Effectful.Internal.Effecttype EffectHandler_ (e :: Effect) (es :: [Effect]) = forall a (localEs :: [Effect]). HasCallStack => e (Eff localEs) a -> Eff es aType signature of a first order effect handler.
interpret_ :: (HasCallStack, DispatchOf e ~ 'Dynamic)=> EffectHandler_ e esThe effect handler.
-> Eff (e ': es) a-> Eff es ainterpret for first order effects.
interpretWith_ :: (HasCallStack, DispatchOf e ~ 'Dynamic)=> Eff (e ': es) a-> EffectHandler_ e esThe effect handler.
-> Eff es ainterpretWith for first order effects.
reinterpret_ :: (HasCallStack, DispatchOf e ~ 'Dynamic)=> (Eff handlerEs a -> Eff es b)Setup of effects encapsulated in the handler.
-> EffectHandler_ e handlerEsThe effect handler.
-> Eff (e ': es) a-> Eff es breinterpret for first order effects.
reinterpretWith_ :: (HasCallStack, DispatchOf e ~ 'Dynamic)=> (Eff handlerEs a -> Eff es b)Setup of effects encapsulated in the handler.
-> Eff (e ': es) a-> EffectHandler_ e handlerEsThe effect handler.
-> Eff es breinterpretWith for first order effects.
interpose_ :: (HasCallStack, DispatchOf e ~ 'Dynamic, e :> es)=> EffectHandler_ e esThe effect handler.
-> Eff es a-> Eff es ainterpose for first order effects.
interposeWith_ :: (HasCallStack, DispatchOf e ~ 'Dynamic, e :> es)=> Eff es a-> EffectHandler_ e esThe effect handler.
-> Eff es ainterposeWith for first order effects.
impose_ :: (HasCallStack, DispatchOf e ~ 'Dynamic, e :> es)=> (Eff handlerEs a -> Eff es b)Setup of effects encapsulated in the handler.
-> EffectHandler_ e handlerEsThe effect handler.
-> Eff es a-> Eff es bimpose for first order effects.
imposeWith_ :: (HasCallStack, DispatchOf e ~ 'Dynamic, e :> es)=> (Eff handlerEs a -> Eff es b)Setup of effects encapsulated in the handler.
-> Eff es a-> EffectHandler_ e handlerEsThe effect handler.
-> Eff es bimposeWith for first order effects.
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.