Skip to content
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

Why does forEach throw away the return value while map/filter pass it through unchanged? #27

Closed
domenic opened this issue Jun 4, 2015 · 4 comments

Comments

@domenic
Copy link
Member

domenic commented Jun 4, 2015

I realize map/filter are provisional. But in general it's unclear to me how the return value/exception is supposed to integrate with all this. Another related question is why any thrown exception gets passed through as a promise rejection while the return value gets ignored.

Can we use iterator map/filter/forEach to inform the design here? I'm not sure how exactly, but it would be nice, because right now it feels like there's a real tension between observables-as-sequences and observables-as-generator-duals.

Is there precedent for this observables-as-generator-duals in other languages or libraries?

@zenparsing
Copy link
Member

The basic propagation story is that with observables, all completion values (whether normal, throw, or return) are pushed up the call stack, instead of flowing down the stack. Throws propagate up the stack via the observer's "throw" method. If the observer doesn't have an error handler, or itself throws an error, then the exception will propagate back down the stack.

Let's take map with argument fn.

With iterators, map would get the next value from upstream, execute fn on the value, and then yield the result to the caller. If an error occurred getting the value from upstream, or during execution of fn, then that error would propagate down the stack as a thrown exception.

With observables, map would receive the next value from upstream, execute fn on the value, and push the result to the observer. Any errors from upstream will be forwarded (pushed) to the observer. If an error occurs during execution of fn, then we catch that error and attempt to push it to the observer. Since we don't map over the return value, the return value is simply forwarded (pushed) to the observer.

forEach is a little different, in that it cannot be chained any further and errors are propagated with a promise. We (I think) decided in the other thread that it should return Promise<void> instead of Promise<R>, with R being the type of the result value. The intention there is to provide a combinator that allows easy integration with promises in general and await specifically.

Does that help at all?

@domenic
Copy link
Member Author

domenic commented Jun 4, 2015

A little bit. I'll try to think on it more before replying. The up-then-otherwise-down seems a bit schizophrenic, but maybe after I run through it a few times it'll make more sense.

I still don't think this justifies removing the return value from forEach, unless you also remove the exception. Although, hmm, then what would you do for reduce? I guess that would have to throw away the return value, so maybe it makes sense to say that sequence terminators throw away the return value, but any exceptions "bubble"? (The path along which they bubble seems very strange though... will think on it more.)

@jhusain
Copy link
Collaborator

jhusain commented Jun 8, 2015

ForEach is meant to approximate For...of behavior which drops return value. Trying to approximate behavior of eventual for...on, to make the refactoring path easy.

Closest example to generators as streams would be itertools on pythons. Will be interesting to examine what they did here. There are also open questions about how to handle return values in merging operations. The two options seem to be to drop the return value, or provide an aggregation function ( a la reduce) to accumulate them somehow.

@zenparsing
Copy link
Member

forEach is out for now

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants