-
Notifications
You must be signed in to change notification settings - Fork 2
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 are async/generators privileged here? #9
Comments
Explanation here. It's essentially centered on the way the function is semantically declared. Since these are concrete semantic constructs within JavaScript itself, it makes sense to honor their declared semantics during composition itself. |
I don't understand what that link is trying to say. In particular, (1) at the link you provided needs some expansion, as I can't tell what it's trying to say. (It's particularly confusing that one of the code samples uses async while the other uses iterators - I can't tell what differences are intended to be instructive versus which are accidental.) |
One is an |
Oh, so they're not meant to be opposing examples, but supporting each other, ok. This doesn't address my concern, tho - there are other types of "value containers" that would be useful to integrate into a function-calling shorthand like this, so why are iterators/asyncs specially called out here? It seems useful to have this driven by a more generic mechanism. It also brings up a further problem - semantic intent regardless, it's generally not possible to distinguish between an iterator-returning function and a generator function, and nothing else makes this distinction. As such, libraries currently implement iterators either way, whichever seems easier for their particular case. Same, but even stronger, for async vs promise-returning - there is no particular reason to prefer this:
over this:
when coding - the two are identical in meaning, and the latter is shorter by twelve characters! But the former will trigger your magic upgrading, while the latter won't. That seems pretty unfortunate and magical - the user of a library needs to be intimately familiar with the internal implementation details of their library to understand what'll happen when they pipeline its functions, and the author of a library needs to be aware that there is a difference, and make an affirmative choice for each individual iterator/promise function whether it, semantically, returns an iterator/promise or multiple/async values, and code accordingly. This seems even more problematic! Thus the thrust of my issue - this sort of functionality seems useful, but it shouldn't be magic and restricted. What we probably actually want is a small stable of pipeline variants - one does normal function calls, one does functor-map (handling iterators and promises in the magic way you want), maybe one does chain/bind; the latter two can invoke symbol-keyed methods on the object for customizability. This puts the context-switch in the author's hands, rather than keying it to an otherwise-arbitrary choice of the library authors. |
I believe the advantages far outweigh the disadvantages. I don't believe it's "magic" in light of how the functions are declared. It's only magic if you have to insist that they are the same as the promise/iterator returning equivalents, which they are functionally, but not semantically. If completely unsure, they also resolve to different types: Can you give a usage example of your proposed approach? |
There is literally no semantic difference between the two promise examples I gave - convenience wrappers around more complex function calls are common, and an otherwise-irrelevant syntax difference shouldn't have large functional differences in this one and only case. As others have argued before, it's intentionally the case that the stdlib doesn't distinguish between AsyncFunction and a promise-returning Function, or a GeneratorFunction versus a function that returns an iterator. These are not meant to be significant differences, they're just implementation details. |
By semantic, I mean a function's declared output value. As you probably already know, an async function is declared to return its promised value (this probably goes without saying): async ()=>5 This is not insignificant, and is the entire crux of why its reasonable to expect the declared output (e.g. The original intention of the async/generator function features are irrelevant here, as it's really all about how a typical developer in the future would logically expect the function they declared to be treated, even if they don't know anything about promises or iterators. And that's the point: async and generator functions can be both declared and utilised without the developer having any knowledge about promises or iterators. They effectively hide those underlying implementations. Finally, this proposal is a function composition operator. And there are only 4 types of functions in existence: Of course I take your point about the library function ambiguity when not documented, but I believe that can be solved by documentation, and even in its absence is inspectable via |
My precise point is that the committee, very intentionally, considers it important to treat Any attempt to propose something that draws a significant distinction between these two cases is going to fail to pass the committee.
Because the committee doesn't want to treat GeneratorFunction different from a function returning an iterator, or AsyncFunction different from a function returning a promise, then as written this can't be a function-composition operator; by necessity it's an operator for calling functions on at least three datatypes (functions, iterators, and promises), with different functionality for each. |
@tabatkins I am precisely arguing that there should be a departure from that resolution, for the reasons stated. It has to be that the advantages outweigh the drawbacks, for which I think this appears to me to be a clear case. I am happy to be convinced otherwise, even though I have not yet been. And just to be clear, the operator does not operate on promises or iterators, but on |
From what I can tell, your argument for departing from that resolution is that it makes it easier to compose the functions in the correct way; you can tell before evaluating the function that it's an AsyncFunction, rather than having to wait until you see its return value is a Promise, etc. My point in opening this thread, tho, is that:
Like, I'd love to have an (Option-returning) Map.get() method, and be able to say |
@tabatkins How would you accomplish what you want, as a feature in the language? I would say any implementation of support for that could be confusing to read. But you might have a way for that that is not confusing. On the other hand, Not sure if I missed your point though... |
As a direct solution, by baking in the combinator as a different glyph. If Here's an example showing this as useful: So, in today's Map, If, instead, Translated over to monadic-pipeline, it would be written as Or if you just wanted to perform some operation on the result of the first This then applies equally to iterators, async, and async iterators; the first two are monads, the second is a nested monad.
But of course it also works with any other monad; Option, Iterator/Array, and Promise are just three of the most common monads among functional languages. |
The big feature that makes this different from some of the other pipeline proposals is that if the function is a generator and/or async, it automagically alters how it calls the remaining chunks of the pipeline, and changes the type of the implicit function created by the pipeline to be generator and/or async as well.
This sort of functionality is useful in general, tho - there are plenty of "types" of values that I might want to have special calling behavior. For example, one function might return a Maybe value, representing a possibly-failed operation, and want to chain it to the next function in the pipeline only if the first operation succeeded...
This is a roundabout way of saying that this proposal is just giving us fmap (call, but for functors), but artificially restricted solely to the iterator and promise functors. It would be much more compelling if it figured out a way to give us arbitrary fmap and/or bind.
The text was updated successfully, but these errors were encountered: