-
Notifications
You must be signed in to change notification settings - Fork 56
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
MonadBaseControl IO Session #144
Comments
The "monad-control" hierarchy has a controversial reputation in the community. Instead of implanting it in the "hasql" lib, it can be provided as an orphan instance. You can have it in your codebase or release it as one of the extension libraries. |
Can an orphan instance actually be provided? I thought it would not be possible since the constructor for Session is not exported. |
It's safe to execute Now, in many words. Session is defined as newtype Session a =
Session (ReaderT Connection (ExceptT QueryError IO) a) So |
|
This is what I thought the instance would look like
For Hasql.Session I would ofc use |
All you need now is the following function: liftSession :: Session a -> MySess a Which is trivial to implement in terms of Yeah. Not exactly the orphan instance implementation, but should work. |
So your recommendation is to use my own session abstraction for this instead of the one provided by hasql? If so, would you feel differently about a MonadUnliftIO instance? |
If you happen to need these abstractions it's likely that you have some monad transformer stack there. You can just add I'm failing to see the real need for binding "hasql" with any one of those abstractions. I'm open for discussion, of course, but I must say that I'm quite skeptical on the matter. |
Perhaps it would help I describe the use-case a bit, then. I was looking into adding support for streaming results to rel8, something like https://hackage.haskell.org/package/rel8-1.2.1.0/docs/Rel8.html#g:38 but returning an effectful stream instead of a list. It would be effectful because it would stream results from the db using a cursor to avoid materializing all the rows in memory at once. (The streaming library I am currently working with is Following this idea, I found So now I want to write a function which will take a query and returns a stream of values produced by And that I think should catch you up to where I am in this exercise. If I took a wrong turn somewhere, please let me know. And just to concretely state what my current goal is, I wish to produce a resource-safe |
Interleaving IO with streaming from cursor is something best avoided, because that requires keeping a transaction open for the duration. Transactions better be as short as possible, because they come with overhead. Locks and etc. But if you still need that, why not |
I believe that using EDIT: FWIW thank you for taking the time to discuss this. |
I agree. How about if I add a function like
In that case in combination with |
Yup, |
Released in 1.5 |
Thanks so much! In case anyone shows up to this issue looking for a MonadBaseControl instance, here it is (and others), in case I don't end up putting them in a library. -- pseudo-newtype coercion helpers
wrapSession :: ReaderT Connection (ExceptT QueryError IO) a -> Session a
wrapSession m = do
conn <- ask
res <- liftIO $ runExceptT $ runReaderT m conn
either throwError pure res
unwrapSession :: Session a -> ReaderT Connection (ExceptT QueryError IO) a
unwrapSession m = ReaderT $ \conn -> ExceptT $ run m conn
-- monad-control
instance MonadBase IO Session where
liftBase = liftIO
instance MonadBaseControl IO Session where
type StM Session a = StM (ReaderT Connection (ExceptT QueryError IO)) a
liftBaseWith runInIO = wrapSession $ liftBaseWith $ \m -> runInIO (m . unwrapSession)
restoreM = wrapSession . restoreM
-- exceptions
instance MonadThrow Session where
throwM = wrapSession . throwM
instance MonadCatch Session where
catch action handler = wrapSession $ catch (unwrapSession action) (unwrapSession . handler)
instance MonadMask Session where
mask f = wrapSession $ mask $ \u -> unwrapSession $ f (wrapSession . u . unwrapSession)
uninterruptibleMask f = wrapSession $ uninterruptibleMask $ \u -> unwrapSession $ f (wrapSession . u . unwrapSession)
generalBracket acquire release use = wrapSession $ generalBracket (unwrapSession acquire) ((unwrapSession .) . release) (unwrapSession . use)
-- unliftio.
-- didn't use the wrapper helpers here because there is no generally-accepted ExceptT instance for MonadUnliftIO. instead I turn MonadError errors into exceptions which are then turned back into errors (see https://github.com/fpco/unliftio/issues/68 for details)
instance MonadUnliftIO Session where
withRunInIO inner = do
conn <- ask
res <- liftIO $ try $ inner $ \sess -> do
run sess conn >>= either throwIO pure
case res of
Left e -> throwError e
Right a -> pure a
|
Session
could be given a MonadBaseControl instance (and I would be happy to do it), but since it used to have one and no longer does I assume it is intentionally missing. I tried finding information on why it was removed, but to no avail. Since I couldn't find any info, and because such an instance would be useful to me, I thought I would file an issue to ask: should Session have a MonadBaseControl instance, and if not, why?The text was updated successfully, but these errors were encountered: