seanmonstar / warp

A super-easy, composable, web server framework for warp speeds.
https://seanmonstar.com/post/176530511587/warp
MIT License
9.59k stars 723 forks source link

Documentation of meaning of SSE into_a and into_b #296

Closed dylanede closed 3 years ago

dylanede commented 5 years ago

These functions (ServerSentEvent::into_a and ServerSentEvent::into_b) seem very opaque and their documentation is not very helpful as to what they are used for. The example code for the sse module uses them but does not explain what they are doing. It would be helpful if the documentation was improved.

remexre commented 4 years ago

@dylanede does this looks like sufficient documentation? https://github.com/remexre/warp/commit/c65678b0125247c46cb0a3fac251f1f8b5756a2d (will PR if so)

jhgg commented 4 years ago

@remexre reading that PR - I still don't understand the situation into which to use into_a and into_b, and the docs here:

https://docs.rs/warp/0.1.20/warp/filters/sse/index.html#example confuse me even more D: Why is into_a().into_b() and into_b().into_b() a thing?!

remexre commented 4 years ago

This needs rewording to sound more documentationy, but:

into_a and into_b serve equivalent purposes: to allow returning multiple ServerSentEvent types without needing a trait object. One could write either:

fn foo() -> Box<dyn ServerSentEvent> {
    if something() {
        data("foo").boxed()
    } else {
        comment("foo").boxed()
    }
}

or

fn bar() -> EitherServerSentEvent<impl ServerSentEvent /* from data */, impl ServerSentEvent /* from comment */> {
    if something() {
        data("bar").into_a()
    } else {
        comment("bar").into_b()
    }
}

// or, since EitherServerSentEvent: ServerSentEvent

fn bar2() -> impl ServerSentEvent {
    if something() {
        data("bar2").into_a()
    } else {
        comment("bar2").into_b()
    }
}

Depending on the types involved, the latter can be more efficient. (Whether it's efficient enough to be measurable is a different question...)


One might chain .into_a().into_b() to be able to return more than two different types. For example,

fn baz() -> EitherServerSentEvent<EitherServerSentEvent<impl ServerSentEvent /* from data */, impl ServerSentEvent /* from comment */>, impl ServerSentEvent /* from event */> {
    if something() {
        data("baz").into_a().into_a() // A(A(data("baz")))
    } else if something_else() {
        comment("baz").into_b().into_a() // A(B(comment("baz")))
    } else {
        event("baz").into_b() // B(event("baz"))
    }
joachimm commented 4 years ago

Sorry for dropping in, I am a Rust novice, playing around with Warp. I got stuck on this when running the Sse. examples.

Please correct if any of the below is wrong.

Thanks to the above answers I figured out the intent (avoid boxing by building a fixed size type, I kind of guessed that was the purpose since the boxed function is in the same trait, but couldn't really move forward ) but not how/why it works language wise. I assume that Rust compiler somehow calculate an EitherServerSentEvent<A, B> that is correct for all possible return types?

Is there some compiler documentation that tells how this works? Is there some academic term for this that one can search for?

This is what is confusing to me:

// From src/filters/sse.rs
       /// Convert to either A
    fn into_a<B>(self) -> EitherServerSentEvent<Self, B> {
        EitherServerSentEvent::A(self)
    }

    /// Convert to either B
    fn into_b<A>(self) -> EitherServerSentEvent<A, Self> {
        EitherServerSentEvent::B(self)
    }

when calling data("baz").into_a().into_a(), how is the B type resolved? What does it mean? I realise this is not a warp specific question.

The A(A(*)) explanation only confused me more (sorry, I am sure it is helpful to others), but triggered me to rewrite it using types instead, which made me I realise how things could be solved, to figure out one type that satisfies all cases.

// shortening EitherServerSentEvent -> ESSE
fn baz() -> EitherServerSentEvent<EitherServerSentEvent<impl ServerSentEvent /* from data */, impl ServerSentEvent /* from comment */>, impl ServerSentEvent /* from event */> {
    if something() {
        data("baz").into_a().into_a() // ESSE<ESSE<SseData, _> _>
    } else if something_else() {
        comment("baz").into_b().into_a() // ESSE<ESSE<_,SseComment,> _>
    } else {
        event("baz").into_b() // ESSE<_,SseEvent>
    }

// using _ to mean a wildcard type, the (only?) possible solution is  ESSE<ESSE<SseData, SseComment>, SseEvent>
remexre commented 4 years ago

Is there some compiler documentation that tells how this works? Is there some academic term for this that one can search for?

It looks like you're using it right below as far as I can tell. This is a consequence of unification in Hindley-Milner type inference.

EDIT: The Wikipedia article doesn't do a great job of explaining Hindley-Milner, now that I skim it; I'd recommend googling around elsewhere. As a self-plug for a resource, I have an implementation of Hindley-Milner in Rust as part of another project; it's Rust 2015 code, but as far as I know, it doesn't use much that's changed since.

jhpratt commented 4 years ago

I presume benchmarks have been run at some point demonstrating that dyn is a net negative? If not, it should be just to confirm a suspicion.

seanmonstar commented 4 years ago

I believe we should probably redesign the sse module to work more like ws does.

jhpratt commented 4 years ago

How difficult do you think a redesign would be for someone who hasn't touched warp's code before? I've worked on Rocket a bit in the past.

seanmonstar commented 4 years ago

I don't think either are that complicated. Probably the sse is too clever, in hindsight. For a newcomer to rust, I'd say skip. It's probably medium difficulty.

jxs commented 3 years ago

it seems this can now be closed as https://github.com/seanmonstar/warp/pull/663 has been merged and ServerSentEvent::into_a and ServerSentEvent::into_b have been deprecated