-
Notifications
You must be signed in to change notification settings - Fork 16
Consolidate APIs #104
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
Consolidate APIs #104
Conversation
src/HTTPure/Body.purs
Outdated
type Body = Stream.Readable () | ||
|
||
--size :: Body -> Effect.Effect Int | ||
--size body = pure 5 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ignore this, was just here for some testing and I forgot to delete it
0024f61
to
3fa4281
Compare
src/HTTPure/Body.purs
Outdated
import HTTPure.Streamable as Streamable | ||
|
||
class Streamable.Streamable b <= Body b where | ||
size :: b -> Effect.Effect (Maybe.Maybe Int) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a Maybe
because it won't be present for chunked responses
044bc2a
to
3d22169
Compare
src/HTTPure/Streamable.purs
Outdated
toStream = createStreamFromString | ||
|
||
instance streamableBuffer :: Streamable Buffer.Buffer where | ||
toStream = createStreamFromBuffer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@hdgarrood @paf31 I see you guys are the major committers to https://github.com/purescript-node/purescript-node-streams... This module feels like something that belongs there but it's not really a direct mapping to Node APIs... what do you guys think? If you want it, I'm happy to submit a PR to put it there
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That’s not the sort of thing I’d want to put in the node bindings - as you say, it doesn’t really correspond to any part of the node api, and also lawless typeclasses are something I’d want to avoid if possible anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense, thanks!
Could you briefly describe your idea of how chunked responses would be done with this approach? |
@akheron basically, we'd just add typeclass instances for instance streamableChunked :: Streamable (Stream.Readable ()) where
toStream (Chunked stream) = -- Take `stream` and inject chunk sizes between chunks
instance bodyChunked :: Body (Stream.Readable ()) where
size = pure Maybe.Nothing Then we just need to modify We could also do something more explicit, such as adding a boolean |
3d22169
to
a137433
Compare
a137433
to
3db853b
Compare
I see. Making class Body s where
writeToStream :: Stream.Writable -> s -> Effect Unit
size :: s -> Maybe Int This would avoid resorting to JavaScript to create a stream for strings and buffers, as they can be directly written to the response. It wouldn't make it any harder to pump a readable stream to the response. And it would allow request handlers to provide a callback or some other means to generate data on the fly in pure PureScript. Something like this: -- writeData takes byte count, buffer of data and a boolean flag
-- denoting whether this is the last chunk
HTTPure.response 200 \writeData -> do
-- get bufSize and buf somehow
writeData bufSize buf true |
EDIT: Ignore all of my inane rambling above. Using a partial function works beautifully and solves both of the above problem. Working on wrapping up unit testing in the commit now, will have it in shortly. Getting rid of class Body b where
size :: b -> Effect.Effect (Maybe.Maybe Int)
write :: b -> HTTP.Response -> Aff.Aff Unit
type Response =
{ status :: Status.Status
, headers :: Headers.Headers
, writeBody :: HTTP.Response -> Aff.Aff Unit
, size :: Maybe.Maybe Int
} |
23890e6
to
9b11a94
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Beautiful 👍
Hi, I want to ask about typeclass approach as I'm interested in functional API design in general. This is not a complain in any way because I really appreciate your work on this framework! I have used purescript hyper and it has the same exact approach (typeclass based API) which for me makes it really hard to use... I've had also used purescript-affjax before it transitioned away from typeclasses and I think that current API which allows explicit selection of a response type is nicer for end user like me. I just don't have to spend time debugging type inference issues related to instance selection any more. I allow myself to ask some questions regarding type classes usage in httpure. Please ignore them if you don't have spare time or just don't like to answer not very thoughtful questions (for sure I should just read all commits related to these pieces of API before asking about anything).
|
The biggest drawback with the data constructor approach was that it required a whole new set of helpers for each body type. So, for instance, we have Another big plus is that it makes it a lot easier for end users to define custom body types. You can always define an instance of the newtype JSON = JSON String
instance jsonBody :: Body JSON where
defaultHeaders = const $ header "Content-Type" "application/json"
write (JSON body) = write body
...
router :: Request -> ResponseM
router = const $ HTTPure.ok $ JSON "{ ... }" All this said, note that all the work here is pre-0.8 release and is subject to revisiting. The API is by no means set in stone. I'm more than happy to revisit, and I'm certainly curious to get some more specific examples of exactly why a typeclass would be difficult on an end user? Do you have any to share? |
Hi @cprussin, Sorry for a late response. I've really free (offline) Weekend and really busy Monday. Regarding problems with typeclass approach I know that because of problems with type inference in our hyper app we ended up with monomorphic type signatures written by hand... we wasn't really able to help it infer types automatically. I think I can provide some code samples, but I'm not sure if they help a lot. I wonder if we could implement dual approach - something like I'm going to test some other scenarios (maybe |
P.S. Regarding direct record passing - these API can be changed to be more polymorphic like: response :: forall r. Status.Status -> Body r -> ResponseM I'm not sure if this could be really useful, but who knows ;-) |
So, I have a few points about this:
So, all in all, I think, given real examples of where the typeclass-based API might be problematic, I'm more than happy to consider alternatives. But without real examples to munch on, I'm not keen on avoiding basic language features that are built to solve exactly the kind of problem that we're using them to solve. UPDATE: I've done a bit more research around this, looking at sources such as http://www.haskellforall.com/2012/05/scrap-your-type-classes.html. It sounds like the strongest arguments in favor of record passing instead of typeclasses from the Haskell community are based on the way typeclasses worked in H98, and a general aversion to Haskell language extensions. Neither of these arguments are applicable to a purescript world. So, this brings me back to my earlier point: I'd like to see examples of where typeclasses are problems, rather than avoid them simply out of general mistrust. |
@cprussin thanks for response! Maybe I'm overemphasizing the problem here. Of course a lot of arguments in such a discussion is a matter of a personal preference. I don't want to copy arguments from "when to use type classes" google results, but here are responses from many really prominent haskell developers on the topic:
I think that it is a matter of design direction in general. From my perspective if many of such simple interfaces are going to be built using typeclasses just to ease implementation and provide overloading they will finally cause a lot of headaches. It is just dangerous practice to start with from my perspective. If we stick to httpure in our current project maybe I will be able to provide some examples.
I think that good type inference and possibility to rely on the compiler when programming with "type holes" is a huge benefit of working with PS. Good style is a different matter for me and it shouldn't be forced by an API which complicates inference flow.
I can assure you that I don't have aversion to type classes in general and I use them a lot in my libraries too... It is necessary tool for any generic programming in PS ;-) I hope I will be able to provide more concrete examples to this discussion, but like I said I'm afraid that in this small context it will be a matter of style and taste... |
Ok, so how about I propose this: I am skeptical that there is any problem with the direction we're going in, but I'm not outside the realm of being convinced. Since this is a minor sub-1.0 release, the API is subject to change without a major version before 1.0. Let's leave it as-is for now, as it does provide tangible benefits in terms of simpler, more concise APIs. However, I'll open a ticket assigned to you to revisit before 1.0. Hopefully, we can find some more concrete sample code before then and can use it to come to a more well informed decision. Does that sound reasonable? EDIT: PS I really appreciate you taking the time here to raise your concerns. Thank you! |
Yeah, cool! Thanks again for your work on httpure! |
Thanks @cprussin! |
This is a
mostly functional WIPinvestigation into consolidating APIs for different response types (string, binary, stream, etc). With this approach, it's possible to use the response helpers for any body type. This approach also sets up for a fairly obvious path for chunked responses. I'd like to get feedback on it sooner than later, which is why I haven't waited until this PR is totally complete to open it up. Work that remains before I'd be ready to merge this:(possible) ShouldEDIT: With the changes, there's no longer aStreamable
be put intoNode.Stream
instead?Streamable
typeclass.Lookup
typeclass, so things likerouter { headers } = HTTPure.ok' responseHeaders $ headers !@ "X-Input"
yield a type error. I need to figure out what's wrong here. EDIT: This can be solved with a functional dependency on theLookup
typeclass, which I had planned on doing anyways. I'll put the code in this PR to add the functional dependency, and will add a follow up PR eventually to take advantage of the functional dependency to simplify the APIs a bitEDIT: This PR is now merge-ready!