Remove dependence on MonadBaseControl?
We depend on MonadBaseControl for saving and restoring the state of monad when we fork a concurrent thread. This issue is track if we can figure out the minimal requirement for this, MonadBaseControl may be too powerful and perhaps we can do with something simpler.
Related to this. Does anyone have ideas? It doesn't have to be ideal for now IMO. One goal is to get the latest MonadResource to work with streamly.
Even if we figure out and create a simpler/less powerful type class it may take time to evolve the API. If we really want resourcet functionality with streamly, I think the easiest/fastest route as of now is to fork a package from http://hackage.haskell.org/package/resourcet-1.1.11, just expose the minimal acquire/release/run APIs to start with and evolve from there based on need.
But before doing that I think it may be a good to have a use case and see that the use case is not possible to express by any existing APIs from streamly or by some low hanging tweaks to any of those APIs.
@harendra-kumar
...it may be a good to have a use case and see that the use case is not possible to express by any existing APIs from streamly or by some low hanging tweaks to any of those APIs.
Doesn't it ease composition? In a streaming pipeline that reads, reads some more, writes, etc. you will avoid the callback hell of bracket etc. if every component in the pipeline makes use of MonadResource. The resource allocation is handled at the monad level.
Maybe I misunderstand something. Can the existing API already do this in a different way?
Updated this in #395 - using resourcet package with MonadBaseControl.
{-# LANGUAGE FlexibleContexts #-}
module RunResourceT where
import Control.Exception (catch, mask, throwIO)
import Control.Monad.IO.Unlift (MonadIO(..), MonadUnliftIO, withRunInIO)
import Control.Monad.Trans.Control (MonadBaseControl, control)
import Control.Monad.Trans.Resource (createInternalState)
import Control.Monad.Trans.Resource.Internal
(ResourceT(..), stateCleanupChecked)
runResourceT :: MonadBaseControl IO m => ResourceT m a -> m a
runResourceT (ResourceT r) = control $ \run -> do
istate <- createInternalState
mask $ \restore -> do
res <- restore (run (r istate)) `catch` \e -> do
stateCleanupChecked (Just e) istate
throwIO e
stateCleanupChecked Nothing istate
return res
@hydramo Yes, I can imagine in a traditional imperative style composition and with too many resources resourcet could be easier to deal with. However, in a fully idiomatic streaming composition I would imagine that we will share streams instead of sharing handles across computations and this problem would probably be less severe.
@harendra-kumar The MonadResource way is more general. You can for instance safely create and return a new stream:
createStream :: (MonadResource m) -> Stream m Int
Without it, you're forced to do something like this (bracket style):
createStream :: (Stream m Int -> IO a) -> IO a
The first is more general because using the first, you can create the second, not the other way around.
For now, I will try your customized runResourceT function. But going forward, can you think of ways to make streamly compatible with MonadResource out of the box? It seems to be the standard resource allocation type class in the Haskell space and this would bring it to par with the other streaming libraries.
I wish I could do it, but I just don't have enough expertise...
I think MonadBaseControl is used for two purposes: 1. for capturing the state of the computation when forking threads; 2. for dealing with exceptions. I haven’t dived deep into this yet, but on the surface it looks to me like forking a computation in any monad that is not MonadUnliftIO is a recipe for disaster (e.g. as is shown in https://github.com/composewell/streamly/pull/105#issuecomment-425735499) and so is trying to deal with exceptions. That’s why I was actually surprised to see that streamly uses MonadBaseControl rather than more restricted, but guaranteed to be correct MonadUnliftIO.
Speaking about the future, I believe that MonadUnliftIO is such that if you develop some reasonable concurrent semantics for a monad transformer, there will be a way forward by replacing MonadUnliftIO by a wider class. Personally I think about this way: MonadUnliftIO is the class of monads that allow unlifting one action; concurrency requires being able to unlift multiple actions, so it can be expressed by another type class, say, class MonadUnliftIO m => MonadConcurrentIO m. In this sense MonadConcurrentIO is the same thing as MonadBaseControl IO except that the latter has way too many instances even when it leads to unpredictable or plain wrong behaviour.