Polysemy: Embed

While you could perfectly stay in pure computation, using already defined Monad (such as IO) is a widespread usage.

That's what Embed has been designed for:

newtype Embed m (z :: Type -> Type) a where
  Embed :: { unEmbed :: m a } -> Embed m z a

used as follows:

logic :: Member (Embed IO) r => Sem r ()
logic = do
  embed $ putStrLn "Hello, world!"

finally we have runM:

runM :: Monad m => Sem '[Embed m] a -> m a

usable as:

runM logic

See the full the code here.