-
Notifications
You must be signed in to change notification settings - Fork 29
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
Runtime error using Effectful + Conduit + Servant + UnliftIO #219
Comments
Not really, seems like a perfectly reasonable use case 🙂 The problem you're hitting is usage of the unlifting function long after the effects it refers to have gone out of scope. Note that it's called when the conduit is run, not when it's constructed and this happens after you return from the handler, so even outside of the call to There are two workarounds:
stream :: Reader String :> es => Eff es (ConduitT () Item IO ())
stream = do
name <- ask
pure $
C.yieldMany [1 ..]
.| C.takeC 10
.| C.mapMC
( \i -> pure $ Item i name
) Of course this doesn't work if you actually need to run some effectful function inside the conduit. In this case you need to copy the environment. A utility function is needed: import Effectful.Dispatch.Static
import Effectful.Dispatch.Static.Primitive
import Effectful
withClonedEnv :: Eff es a -> Eff es a
withClonedEnv action = unsafeEff $ \es -> unEff action =<< cloneEnv es and then stream :: (IOE :> es, Reader String :> es) => Eff es (ConduitT () Item IO ())
stream = withClonedEnv $ do
withSeqEffToIO $ \runIO -> do
pure $
C.yieldMany [1 ..]
.| C.takeC 10
.| C.mapMC
( \i -> runIO $ do
name <- ask
pure $ Item i name
) will work. Looks like I missed this use case, so there is no unlifting strategy that would give you this option easily 😞 |
I think I'll add |
That's what I suspected was happening, though I was confused as to how it was working with
I realize that the example was super artificial, but I was just trying to keep the code small. As you guessed, I would actually like to run these side-effects in my custom monad, but I couldn't come up with a better example :) Does cloning the env like this have any downsides? For example, if I use this technique with a
No problem, thanks for the helpful response! It sounds like adding |
Yes, local flavour of state is forked at this point. |
I just stumbled into the same issue with Scotty and a simple StreamingBody result type. Incidentally I have used Effectful, Servant and Streaming together without issue because I used Servant's
I don't think Scotty has a hoist so I may have to go with |
Sorry, disregard my last reply - I was a bit hasty and didn't fully parse your message @goertzenator ! |
Aha! I got Scotty working with
|
Thanks for the tip! I tried this out, and it worked -- with a caveat. I was required to use the Were you performing side effects in your Thanks! |
Thanks for pointing out I am indeed doing side effects in the streaming response. Looking at my code I ejected the
Making that look more concrete gives:
The part that I'm not seeing in your code is the |
Your gist works, but with a caveat. If your attempt to use any servant-handler-local effect in the conduit, you will be back to square one. E.g. if you modify stream :: (IOE :> es, Reader String :> es) => Eff es (ConduitT () String IO ())
stream = runReader @Int 777 $ do
C.withRunInIO $ \runIO -> do
pure $
C.yieldMany [1 :: Int ..]
.| C.takeC 10
.| C.mapMC
( \i -> runIO $ do
_ <- ask @Int -- makes use of the just-created Reader Int
name <- ask
pure $ name <> " " <> show i
) you'll get the same error as in the beginning because of the same reason - the |
I think I'll add another unlift strategy for this instead of providing Can any of you test if #224 works for you? |
I've updated my example repo and this does indeed fix the problem. Thanks! |
Hello!
I've been experimenting with using
Effectful
alongsideServant
. I also want to useConduit
for streaming responses. BecauseServant
'sConduit
integration expects a handler to return aConduitT () a IO ()
, I decided to useUnliftIO
to run the side-effects inConduit
's pipeline in my custom monad. I know, I know, it's confusing.To make this more clear, I put together an example repo. I've implemented two toy examples -- the first uses
newtype App a = App (ReaderT String IO a)
as my custom monad and the second usestype App = Eff '[Reader String, Error ServerError, IOE]
. Both compile just fine, but when I call the streaming endpoint for theEffectful
example, I get the following runtime error:You can try both versions for yourself. Clone the example repo above, and run the following for the MTL example:
You should now be able to do the following:
Now, try running the effectful version:
If we perform the same curl request, we see our runtime error:
and from the server process:
You can view the code responsible for streaming here.
Apologies for the long-winded explanation, but I figured this would be the easiest way to demonstrate the problem. This feels like it may be a bug.
Let me know if you need any more information.
The text was updated successfully, but these errors were encountered: