dataServantClientmawhereRunClient::ClientMo->ServantClientmorunServantClient::Members'[ Embed IO , Error ClientError ]r=>BaseUrl->Sem(ServantClient': r) a -> Sem r a
Now I could do this with an implementation like this:
But this doesn't work for streaming client responses, which is what I need. I can't just return the response, and resume the rest of my program with it. The rest of my program needs to run insidewithClientM, otherwise the stream closes before the program can consume it.
I have tried the following, using Polysemy.Cont from polysemy-zoo:
runServantClientStreaming::Members'[ Cont ref , Embed IO , Error ClientError ]r=>BaseUrl->(forallb.Semro->IOo)->Sem(ServantClient': r) a -> Sem r arunServantClientStreamingserverrunItm=domanager<-embed$newManagertlsManagerSettingsletenv=mkClientEnvmanagerserverinterpret(\caseRunClientclient->doresult<-callCC(\continue->embed$withClientMclientenv(runIt.continue))fromEitherresult)m
But it's nasty, and I can't quite get it to typecheck. I'm just about out of ideas. Can anyone suggest how I might make this work?
isn't that effect "too specific", i.e. instead have an effect that gives you the data you actually need, and then when you want to run that effect you can use all the ClientM as generated by servant?
the annoying bit here is you're effectively re-describing the API as an effect, but if you work directly with ClientM o it seems to me like you're effectively restricting yourself to only running the effect with servant-client (no pure interpretation)
I don't understand what @Sandy Maguire means by "yolo calling it in main" - you still need to get the info to the "inside" of your effects world somehow (?)
Yes, it is a very specific effect. I do actually have the more general effect which produces the data I need, but my interpreter for that effect leaves this ServantClient effect behind. I think if I move my withClientM call into the general effect then I'll still be left with this problem though, because it still produces a stream as its output, which necessarily escapes the scope of withClientM.
re streaming, yes I saw Polysemy.Input.Streaming. I'm actually using machines for my streaming though, so I have another pair of interpreters for working with this:
runInputFromSourceT::Member(Embedm)r=>SourceTmo->Sem(Input(Maybeo)': r) a->SemrarunInputFromSourceTsource=fmapsnd.runStatesource.reinterpret(\caseInput->dostream<-getstep<-embed$runMachineTstreamcasestepofStop->pureNothingYieldorest->doputrestpure(Justo)Await{}->pureNothing-- Shouldn't be possible in a SourceT?)inputToSourceT::Member(Input(Maybeo))r=>SourceT(Semr)oinputToSourceT=construct(exhaustinput)
@Alex Chapman I implemented http response streaming, with http-client, by passing a consumer to the effect (your ServantClient). It's basically Resource.
@Torsten Schmits can you elaborate, or share any code? I've tried to use Resource for this, but couldn't work out how to get alloc/dealloc arguments for bracket out of withClientM. I've been starting to think that I need to write my own Polysemy version of ClientM to make this work, but I had been hoping that Polysemy would be powerful enough to wrap any API, regardless of its form.
@Alex Chapman With http-client, it's pretty simple, since it has an API for this purpose (Response values contain an IO ByteString that produces the next chunk). I'd happily share some code but I doubt it will help with your issue.
since Servant has hardcoded ReaderT for both server and client, it's quite impossible to integrate with Polysemy, I think. I encountered the same problem when trying to run a server in my effect stack.
although now I see that since the perform... function takes a handler producing IO you can't run a Sem in there. You'll have to dissect and rewrite that part
I'm using polysemy "under" servant-server, i.e. servant-server still handles spawning threads and such, but my handlers run in a Sem by using hoistServer in the end, before trying to serve
I don't feel the need to also have effects for the functionality servant-server is providing, because I don't think I need to test it? after all it's a library
I have some state-esque custom effect that I run with an interpreter that takes the connection pool and executes in IO (via Embed) - runDb let's say
I do runDb, discharge all my other effects, at which point I'm left with a combination of Either and IO, which is basically Handler, so I lift into it
would the same thing not work with your kafka connection?
main=do....letrunServer::(MonadIOm,MonadUnliftIOm,MonadLoggerm)=>m()runServer=withMySQLPoolinfo100\backend->liftIOdorun3000$serverDbbackendputStrLn"server exited."runStdoutLoggingTrunServer-- the handler for my entire apihandler::Members<all-my-direct-effects>r=>ServerTAPI(Semr)handler=...api::ProxyAPIapi=...serverDb::PoolSqlBackend->ApplicationserverDbbackend=serveapi$hoistServerapi(runServerIObackend)handlerwhererunServerIO::PoolSqlBackend->Sem<all-my-effects>a->HandlerarunServerIObackend'=...
So, for my withClientM problem, I have found a solution, albeit a slightly ugly one:
runServantClientStreaming::Members'[ Cont ref , Embed IO , Error ClientError ]r=>BaseUrl->Sem(ServantClientStreaming': r) a -> Sem r arunServantClientStreamingserverm=domanager<-embed$newManagertlsManagerSettingsletenv=mkClientEnvmanagerserverinterpret(\caseRunClientStreamingclient->subst(\continue->withLowerToIO$\unliftIO_->withClientMclientenv(unliftIO.jumpcontinue))fromEither)m
It uses subst from Cont to pack all of the future computations up, and then withLowerToIO (which runs in a new thread!) to run the client, and the rest of the future computations, in IO. And by some miracle it works! I haven't tested it with two long-running servant client sessions though...
Yeah, I was stuck with an Either ClientError a -> Sem r a, but I needed an Either ClientError a -> IO a, so withLowerToIO was the solution. It's very similar to UnliftIO (https://hackage.haskell.org/package/unliftio).
I can't figure out how to use this and errorToIOFinal though. Because using runContM seems to exclude the use of Final IO, and contToFinal comes with a scary warning that I don't understand.
I am trying to wrap a call to
withClientM
fromservant-client
inside aPolysemy effect.
I want my effect to look something like this:
Now I could do this with an implementation like this:
But this doesn't work for streaming client responses, which is what I need. I can't just return the response, and resume the rest of my program with it. The rest of my program needs to run inside
withClientM
, otherwise the stream closes before the program can consume it.I have tried the following, using
Polysemy.Cont
frompolysemy-zoo
:But it's nasty, and I can't quite get it to typecheck. I'm just about out of ideas. Can anyone suggest how I might make this work?
i'd call
withClientM
inmain
and yoloisn't that effect "too specific", i.e. instead have an effect that gives you the data you actually need, and then when you want to run that effect you can use all the
ClientM a
s generated by servant?the annoying bit here is you're effectively re-describing the API as an effect, but if you work directly with
ClientM o
it seems to me like you're effectively restricting yourself to only running the effect withservant-client
(no pure interpretation)I don't understand what @Sandy Maguire means by "yolo calling it in main" - you still need to get the info to the "inside" of your effects world somehow (?)
re the streaming stuff, did you take a look at http://hackage.haskell.org/package/polysemy-zoo-0.7.0.0/docs/Polysemy-Input-Streaming.html?
Yes, it is a very specific effect. I do actually have the more general effect which produces the data I need, but my interpreter for that effect leaves this
ServantClient
effect behind. I think if I move mywithClientM
call into the general effect then I'll still be left with this problem though, because it still produces a stream as its output, which necessarily escapes the scope ofwithClientM
.re streaming, yes I saw
Polysemy.Input.Streaming
. I'm actually usingmachines
for my streaming though, so I have another pair of interpreters for working with this:@Alex Chapman I implemented http response streaming, with http-client, by passing a consumer to the effect (your
ServantClient
). It's basicallyResource
.@Torsten Schmits can you elaborate, or share any code? I've tried to use
Resource
for this, but couldn't work out how to get alloc/dealloc arguments forbracket
out ofwithClientM
. I've been starting to think that I need to write my own Polysemy version of ClientM to make this work, but I had been hoping that Polysemy would be powerful enough to wrap any API, regardless of its form.@Alex Chapman With http-client, it's pretty simple, since it has an API for this purpose (
Response
values contain anIO ByteString
that produces the next chunk). I'd happily share some code but I doubt it will help with your issue.since Servant has hardcoded
ReaderT
for both server and client, it's quite impossible to integrate with Polysemy, I think. I encountered the same problem when trying to run a server in my effect stack.@Alex Chapman there's a function
performWithStreamingRequest
, that should do itmy effect looks like this:
so you'd have to run the servant client perform function where I run
handler
Ah, thanks a lot. I'll give that a try when I get a chance.
although now I see that since the
perform...
function takes a handler producingIO
you can't run aSem
in there. You'll have to dissect and rewrite that partThat's what I was afraid of :D
wonder if anyone has a suggestion for an http framework that goes well with polysemy
(server, that is. http-client is working out fine for me)
and that
perform
function doesn't even run the client, you'd still need to callrunClientM
, so forget thatwould be worth the try to just replace
IO
withm
in all of servant and see what needs to be fixed :slight_smile:I'm using polysemy "under"
servant-server
, i.e.servant-server
still handles spawning threads and such, but my handlers run in aSem
by usinghoistServer
in the end, before trying toserve
yeah same. it's not optimal
what problems did you encounter with this approach?
I don't feel the need to also have effects for the functionality
servant-server
is providing, because I don't think I need to test it? after all it's a librarywell mainly that I cannot share interpreters across requests
so some shared state?
for example, a kafka connection
I have a mysql connection pool that's shared, would it be something similar?
guess so!
I have some state-esque custom effect that I run with an interpreter that takes the connection pool and executes in IO (via
Embed
) -runDb
let's sayI do
runDb
, discharge all my other effects, at which point I'm left with a combination of Either and IO, which is basicallyHandler
, so I lift into itwould the same thing not work with your kafka connection?
and that's the only place where I have to pass in the connection
maybe I'm misunderstanding something :/
you're passing the connection into the warp main function and feeding it into an interpreter?
serverDb
is the bit I was referencingyeah so you have to handle the pool manually, that's what I meant with "not optimal"
well I only create it once and pass it in immediately, I guess it would be annoying if it weren't like that
thanks!
:slight_smile:
so how would the signature of an http server have to look in order to work well with polysemy?
would it be sufficient to parameterize the
IO
?could one use
Embed HandlerT
?or
MFunctor.hoist
?So, for my
withClientM
problem, I have found a solution, albeit a slightly ugly one:It uses
subst
fromCont
to pack all of the future computations up, and thenwithLowerToIO
(which runs in a new thread!) to run the client, and the rest of the future computations, in IO. And by some miracle it works! I haven't tested it with two long-running servant client sessions though...wow!
I will have to try that for the server
I was wondering whether "lower to IO" is a concept that applies here, though I haven't understood it yet
Yeah, I was stuck with an
Either ClientError a -> Sem r a
, but I needed anEither ClientError a -> IO a
, sowithLowerToIO
was the solution. It's very similar to UnliftIO (https://hackage.haskell.org/package/unliftio).yeah that was my reference as well
incredible!
I can't figure out how to use this and
errorToIOFinal
though. Because usingrunContM
seems to exclude the use ofFinal IO
, andcontToFinal
comes with a scary warning that I don't understand.Oh, and there's no instance for
MonadCont IO
and
errorToFinal
forces use ofFinal IO
, so I can't useFinal (ContT IO)
can't you just
Embed IO
, thenembedToFinal
, thenerrorToIOFinal
?I think not, because
runContM
produces a fixed effect list:Sem '[Embed m] a
indeed
so maybe first
runError
, thenfromEither
after?this probably breaks something though
Yeah,
runError
works, dealing with theEither
and finishing with& runContM & runM
:thumbs_up:
I can't believe it but this just fucking works™:
Polysemy is so great