Polysemy: Tactics binding deprecated workaround

In a previous log, we ended with a solution for Resource which wasn't Error resistant.

The solution is to use the soo-deperecated withLowerToIO.

Let's start again with our simpler effect

data BindE (m :: Type -> Type) a where
  BindE :: m a -> (a -> m b) -> BindE m b

makeSem ''BindE

We can express an impure (Embed-base) interpreter:

interpretBindTacticLowering :: forall r. Member (Embed IO) r => InterpreterFor BindE r
interpretBindTacticLowering =
  interpretH $
    \case
      BindE f g -> do
        ma <- runT f
        mf <- bindT g
        withLowerToIO $ \lower _ -> do
          let toIO :: Sem (BindE ': r) x -> IO x
              toIO = lower . raise . interpretBindTacticLowering
          toIO ma >>= toIO . mf

withLowerToIO provides two functions (one to locally interpret a Sem r a to IO a and a finalizer) and expect a IO a, it's defined as:

withLowerToIO
    :: Member (Embed IO) r
    => ((forall x. Sem r x -> IO x) -> IO () -> IO a)
    -> Sem r a

Under the hood, a thread is created (the program should be compiled with -thread) to run the effect stack until IO, 'blocking' thread.

The finalizer does not need to be called, but it indicate the end of the new thread.

Let's see how it goes for Resource:

data Resource m a where
  Bracket :: m a -> (a -> m c) -> (a -> m b) -> Resource m b

We are now able to use bracket:

resourceToIO :: Member (Embed IO) r => InterpreterFor Resource r
resourceToIO =
  interpretH $
    \case
      Bracket alloc dealloc use -> do
        alloc' <- runS alloc
        dealloc' <- bindS dealloc
        use' <- bindS use

        withLowerToIO $ \lower finish -> do
          let runHoE :: Sem (Resource ': r) x -> IO x
              runHoE = lower . raise . resourceToIO
          Expection.bracket
              (runHoE alloc')
              (\x -> runHoE (use' x) >> finish)
              (runHoE . dealloc')

See the full the code here and here.