A software engineer website

Polysemy: IO

Gautier DI FOLCO February 22, 2023 [Haskell] #haskell #polysemy #design #effects systems

Polysemy provides many ways to combine both effects and interpreters, let's start with a simple Db effects:

data Db (m :: Type -> Type) a where
  ExecQuery :: Statement args a -> args -> Db m a

Some libraries provide dedicated Monad which are expensive to run, for example:

runQuery :: Statement args a -> args -> MonadDb a

Would force us to embed it:

runDb :: forall r. Member (Embed MonadDb) r => InterpreterFor Db r
runDb =
  interpret $
    \case
      ExecQuery s args -> embed $ runQuery s args

We have many interpretations options:

embedToMonadIO :: forall m r a. (MonadIO m, Member (Embed m) r) => Sem (Embed IO ': r) a -> Sem r a

If MonadDb is a MonadIO instance, we can use it as such:

runMonadDb $ runM $ embedToMonadIO @MonadDb $ runDb act

Everything will be run as MonadDb.

Under the hood, it uses runEmbedded:

runEmbedded
  :: forall m1 m2 r a.
  Member (Embed m2) r
  => (forall x. m1 x -> m2 x)
  -> InterpreterFor (Embed m1) r

Which is more straightforward to use:

  runM $ runEmbedded runMonadDb $ runDb act

A last option is to use lowerEmbedded.

lowerEmbedded
  :: (MonadIO m, Member (Embed IO) r)
  => (forall x. m x -> IO x)
  -> InterpreterFor (Embed m) r

It creates a run thread to deal with the lowering.

runM $ lowerEmbedded runMonadDb $ runDb act

See the full the code here.