lexi-lambda / freer-simple

A friendly effect system for Haskell
https://hackage.haskell.org/package/freer-simple
BSD 3-Clause "New" or "Revised" License
228 stars 20 forks source link

MonadBaseControl instances #20

Closed ProofOfKeags closed 5 years ago

ProofOfKeags commented 5 years ago

I was looking at trying to use resource-pool in an Eff stack and ran into an issue where I could not get a MonadBaseControl instance for an eff stack. This makes sense to me as there are none defined. Is it possible to define them, and if so would this library be interested in accepting a PR that adds it?

Lysxia commented 5 years ago

You might like to see how extensible-effects did it: https://github.com/suhailshergill/extensible-effects/pull/83

isovector commented 5 years ago

I'd suggest not going through the effort. You generally need MonadBaseControl in order to use bracket and to spin up new threads---both can be accomplished with less weight.

If you want bracket, just put ResourceT IO at the bottom of your stack:

bracket
    :: Member (ResourceT IO) r
    => IO a
    -> (a -> IO ())
    -> (a -> Eff r b)
    -> Eff r b
bracket alloc dealloc m = do
  (key, a) <- send $ allocate alloc dealloc
  result   <- m a
  send $ release key
  pure result

If you want to be able to do async, a rough POC looks like this---start a worker thread, and use an effect to pass messages to it:

data AsyncEff capabilities a where
  AsyncEff
      :: Members capabilities r
      => Eff r a
      -> AsyncEff capabilities ()

startAsyncTaskSlave
    :: Members capabilities r
    => (forall x. Eff r x -> IO x)
    -> IO (InChan (AsyncEff capabilities))
startThreadPool runEff = do
  (in, out) <- newChan 10
  void . async . forever $ do
    m <- readChan out
    async $ runEff m
  pure in

asyncEff
    :: Member IO r
    => InChan (AsyncEff capabilities)
    -> Eff (AsyncEff capabilities ': r) a
    -> Eff r a
asyncEff chan = handleRelay pure bind
  where
    bind eff k = send (writeChan chan eff) >>= k
ProofOfKeags commented 5 years ago

IIUC bracket and withResource accomplish the same thing yeah?

isovector commented 5 years ago

Yup. withResource can't be used here because "Cont isn't an algebraic effect", but the explicit acquire and release semantics of ResourceT do allow for this.

ProofOfKeags commented 5 years ago

Am I to understand that you only need one ResourceT layer to handle an arbitrary number of resource pools?

isovector commented 5 years ago

Yeah -- you can reimplement withResource like this:

withResource
    :: Member (ResourceT IO) r
    => Pool a 
    -> (a -> Eff r b)
    -> Eff r b
withResource pool f = 
  bracket (takeResource pool) (uncurry (flip putResource)) (f . fst)
isovector commented 5 years ago

@lexi-lambda would you accept a PR that provides a blessed bracket effect (and the resulting dependency on resourcet)? This was a stumbling block for me and my company a few years back, and I imagine it's a pretty widespread problem.