Reserving await within Arrow Functions

# Kevin Smith (12 years ago)

I think we should at least consider for a moment reserving await within the body of arrow functions. Consider the following event handling scenario, where lexical this is an important feature:

this.button.on("click", $=> {
    this.getDataAsync().then(data => {
        this.showData(data);
    });
});

If JS gets async functions, then users will naturally want to express such a construct with await:

this.button.on("click", $=> {
    let data = await this.getDataAsync();
    this.showData(data);
});

If we get async functions, then I think that we will want an arrow form. It would be a shame to have to invent new syntax to support it.

Thoughts?

# Brendan Eich (12 years ago)

Thanks for brining this up -- it's a good point. Unlike ES5 strict and 'yield', the cat is not yet out of the bag. We need to commit, though.

# Rick Waldron (12 years ago)

On Wed, Dec 11, 2013 at 5:31 PM, Brendan Eich <brendan at mozilla.com> wrote:

Thanks for brining this up -- it's a good point. Unlike ES5 strict and 'yield', the cat is not yet out of the bag. We need to commit, though.

I suspect there are a few keywords to reserve within new syntactic forms, ie. ClassBody and ModuleBody

# Brendan Eich (12 years ago)

What else? Please specify :-P.

# Domenic Denicola (12 years ago)

I think this largely comes down to whether people think an "async" signifier (whether async, ^, or !) is a good notification to have  that you are about to enter async code.

I remember from a few weeks back we had (yet another) thread on generators-without-the-star, and a few people were strongly in favor of keeping the star no matter what, just as a visual indicator that you were about to enter a block of code that could possibly suspend itself. I imagine those people would want the same for await.

# Mark S. Miller (12 years ago)

Yes. I think the reasons for wanting the "*", whether you agree with them or not, all apply equally to "!" (or whatever the syntax is).

# Rick Waldron (12 years ago)

What else? Please specify :-P.

Well, I'm not actually sure yet, I was using the subject as an opportunity to open up discussion and to note that new reservations should not be limited to arrow functions ;)

# Allen Wirfs-Brock (12 years ago)

On Dec 11, 2013, at 2:44 PM, Mark S. Miller wrote:

Yes. I think the reasons for wanting the "*", whether you agree with them or not, all apply equally to "!" (or whatever the syntax is).

And if there is a syntactic flag like that, I don't see that we need to worry about preserving anything that would only valid within explicitly flag context.

# Brendan Eich (12 years ago)

But to recap the TC39 meeting discussion, we do not believe we can add =>* or =>! -- the latter is arrow with a unary logical negation

expression as the body. Other places to put the * and ! are problematic due to ASI. So what exactly are we gonna do for async arrows, if anything? We said no/defer to generator arrows.

# Brendan Eich (12 years ago)

To say a bit more, if we don't want 'yield' in the body of an arrow to imply that that arrow function is a generator function, then the same argument seems to apply to 'await'. So we need a sigil or equivalent. But =>! is at least as infeasible as =>*. So perhaps we cannot reserve 'await' in ES6 arrows.

# Kevin Smith (12 years ago)

To say a bit more, if we don't want 'yield' in the body of an arrow to imply that that arrow function is a generator function, then the same argument seems to apply to 'await'. So we need a sigil or equivalent. But =>! is at least as infeasible as =>*. So perhaps we cannot reserve 'await' in ES6 arrows.

I think there is reason to think of "arrows generators" and async arrows as sufficiently different cases to warrant different choices.

The design niche of arrows is "lightweight functional mappings". Generators don't define mappings, they define sequences. Hence arrow generators don't make a whole lot of sense.

On the other hand, async arrows make perfect sense as "lightweight functional mappings to Promise<T>".

To my mind, we will almost certainly want async arrows as soon as we have async functions. Sure, we can always introduce ever-more grawlixy notation. But why commit ourselves to that? It costs almost nothing to reserve "await" now.

Let me present a possible future syntax choice:

AsyncFunctionDeclaration:
    "async" "function" BindingIdentifier ...
    "async" [NoNewLine] BindingIdentifier ...

    async function F() {}
    async F() {}

AsyncFunctionExpression:
    "async" "function" BindingIdentifier [opt] ...
    "async" [NoNewLine] BindingIdentifier ...

    let F = async function() {});
    let F = async ƒ() {};

AsyncMethodDefinition:
    "async" PropertyName ...

    ({ async F() {} });

And any arrow function with an await expression is an async arrow function.

The non-arrow productions allow the user to define the base case of an async function without an await expression. For arrow functions, It will be obvious to the user that an arrow containing an await expression is indeed an async function. In fact, that "inference" fits perfectly well with the syntax-light nature of arrow functions.

And the fact that we don't have to introduce unnecessary grawlix makes life easier for those learning the language.

That's one possible future, anyway.

# Kevin Smith (12 years ago)

Why is it always easier to see the errors after hitting that "Send" button, anyway?

Simplified and ASI corrected:

AsyncFunctionDeclaration:
    "async" [NoNewLine] BindingIdentifier ...

    async F() {}

AsyncFunctionExpression:
    "async" [NoNewLine] BindingIdentifier ...

    let F = async ƒ() {};

Slightly off-topic: the reason I'm suggesting using "async" as a contextual keyword instead of function! boils down to audience. The way I see it, the audience for generators are creators of sequence abstractions: intermediate to advanced users. For those users, a sigil-char is just fine. But async functions will be written by users at all levels of experience, and for that audience natural language-inspired syntax is beneficial.

# Forbes Lindesay (12 years ago)

I always want the * to be mandatory for full generator functions as a visual cue. I suspect I would want something similar for async functions but I can see logic to not needing the same visual cue on an arrow function.

Arrow functions would often be significantly shorter, so the visual cue of the yield/await keyword would be more obvious.

# Marius Gundersen (12 years ago)

Couldn't a slight variation be used for arrow functions, rather than adding yet another character in front or behind it? For example, using -> or ~> (unless they have been reserved for something in ES7):

var arrowFunction = (a, b) => a+b;

var generator = (a, b) -> yield a; yield b;

var async = (a, b) ~> (await a) + (await b);
# Kevin Smith (12 years ago)
var arrowFunction = (a, b) => a+b;
var generator = (a, b) -> yield a; yield b;
var async = (a, b) ~> (await a) + (await b);

My thoughts:

Again, generators are not a good fit for arrows - generators define sequences, not mappings.

Secondly, we should be conservative in our usage of arrow-like operators - we only have so many decent looking things left!

Third, looking at your third case (and removing unnecessary parens):

var async = (a, b) ~> await a + await b;

It's completely obvious to the reader (by virtue of the await expressions) that we are looking at an async function. Having a different operator here offers neither the reader nor the parser any real help. It just makes the language more grawlixy and less DWIM.

This line of reasoning only applies to async arrows, and not to async function declarations, expressions, or methods. Again, arrows are by nature syntax-light and tolerate a higher degree of "inference" than other forms.

My point is that we should reserve "await" within arrows to preserve these design options down the road. It can always be unreserved later.

# Brendan Eich (12 years ago)

Kevin Smith wrote:

My thoughts:

Again, generators are not a good fit for arrows - generators define sequences, not mappings.

Yep.

Secondly, we should be conservative in our usage of arrow-like operators - we only have so many decent looking things left!

What's more, ~ has nothing to do with generators, which use * (function*, yield*). Incoherent grawlix is no-go.

Third, looking at your third case (and removing unnecessary parens):

var async = (a, b) ~> await a + await b;

It's completely obvious to the reader (by virtue of the await expressions) that we are looking at an async function. Having a different operator here offers neither the reader nor the parser any real help. It just makes the language more grawlixy and less DWIM.

The issue with 'await' is: apart from sequence vs. mapping, given that arrows have a limited Tennent's Correspondence Principle for not only this and super, is it confusing to have arguments, yield, and await not mean outer "bindings", vs. inner?

This line of reasoning only applies to async arrows, and not to async function declarations, expressions, or methods. Again, arrows are by nature syntax-light and tolerate a higher degree of "inference" than other forms.

My point is that we should reserve "await" within arrows to preserve these design options down the road. It can always be unreserved later.

I think you have a point, whether we make await work or ban it to avoid partial-TCP confusion.

# Alex Russell (12 years ago)

On Thu, Dec 12, 2013 at 9:40 AM, Brendan Eich <brendan at mozilla.com> wrote:

The issue with 'await' is: apart from sequence vs. mapping, given that arrows have a limited Tennent's Correspondence Principle for not only this and super, is it confusing to have arguments, yield, and await not mean outer "bindings", vs. inner?

No.

I think you have a point, whether we make await work or ban it to avoid partial-TCP confusion.

I'll continue to point out that the confusion is largely a product of language lawyering, not real-world expectations of JS behavior. Banning await here is only a solution if you're also not going to ban an async descriptor on the arrow expression. THAT is the mistake.

# Kevin Smith (12 years ago)

Banning await here is only a solution if you're also not going to ban an async descriptor on the arrow expression. THAT is the mistake.

Not sure I follow - can you elaborate? What is an "async descriptor on the arrow expression"?

# Alex Russell (12 years ago)

If you can't indicate that the arrow itself is async somehow (either by prefixing it with deferred or async or using a variant of the arrow itself, e.g. ~=>), then you get into the issue Brendan describes when you allow await inside the body.

# Mark S. Miller (12 years ago)

Putting aside my immediate esthetic reaction, I suggest we consider one of

=>, *=>, !=>
=>, =*>, =!>
=>, *>, !>

Which do you hate least?

Btw, Kevin, I have read and do not understand your argument about why we would not want generator arrow functions but would want async arrow functions. If we could agree on syntax, I still want both.

# Domenic Denicola (12 years ago)

I agree that there is no difference between generator-returning arrows and promise-returning arrows. Using the earlier type-ish notation, where => is a mapping to T, =*> would be a mapping to Generator<T>, just like =!> would be a mapping to Promise<T>.

# Kevin Smith (12 years ago)
=>, *=>, !=>
=>, =*>, =!>
=>, *>, !>

Which do you hate least?

Hate is such a strong world... : )

My aesthetic judgement is that "!" is not a good choice because grawl is bad for beginners. And async functions will touch beginners quite heavily (I predict).

Btw, Kevin, I have read and do not understand your argument about why we would not want generator arrow functions but would want async arrow functions. If we could agree on syntax, I still want both.

Let me restate first.

At a conceptual level, arrows are mappings. "x goes to whatever". That is the niche they fill. That's why they are so beautifully suited to, for example, array iteration functions, promise callbacks, and even event listeners (which can be considered mappings to void).

Async arrows fit right into that niche, except asynchronously. "x goes to Promise<whatever>". They will be well-suited to event listeners, in

particular. (See my OP for an example.)

Generators, on the other hand, are a way of implementing a sequence. As such, they don't fit into that "arrow" niche.

But aren't async functions going to be implemented with generators, basically? Well, yes, but that's just an implementation detail. Generators will probably be used to implement async functions, but they are completely different at a conceptual level.

I believe that once we have async functions (and async arrows), the need for a lightweight generator syntax will largely evaporate.

Or to put the question on more utilitarian ground: given async functions and async arrows, what use cases are left for generator arrows?

# Jeremy Martin (12 years ago)

My apologies if this has already been covered, but can anyone point to the rationale for arrow functions (primarily) targeting mappings?

I think it's fairly self-evident that they are a natural fit for such use cases, but I find it less self-evident that they are inherently not useful for others (e.g., generators). I feel like I keep seeing the following (implied) argument:

  1. Arrow functions are good for mappings
  2. Generators aren't mappings
  3. Arrow functions aren't good for generators.

Grammar problems aside, I guess I don't follow the reasoning to #3. Conversely, I think that a case could be made that minimizing the differences between "normal" functions and arrow functions is desirable in its own right.

# Kevin Smith (12 years ago)

I think it's fairly self-evident that they are a natural fit for such use cases, but I find it less self-evident that they are inherently not useful for others (e.g., generators). I feel like I keep seeing the following (implied) argument:

  1. Arrow functions are good for mappings
  2. Generators aren't mappings
  3. Arrow functions aren't good for generators.

That would be me. : )

I'm trying to look at things not just in terms of "what would be useful", although that's important. I'm looking at the overall design of the language, and understand the way all the parts fit together into a beautiful whole.

It's an aesthetic thing, which I can appreciate may be a little difficult to swallow.

FWIW, I would restate the argument as follows:

  1. Arrow functions "express" mappings
  2. Generators "express" sequences
  3. Don't muddy the waters

Anyway, I'm not really trying to convince anyone. I just think we should reserve the right to have this discussion later, and not prematurely commit to "=!>" or whatever.

# Jeremy Martin (12 years ago)

:)

Thanks for clarifying. I guess the part that I'm still questioning, though, is:

  1. Arrow functions "express" mappings

This is of course true, but is that not a bit exclusive? They're also great at:

  1. Non-mapping iteration (e.g., forEach())
  2. Lightweight callbacks
  3. Killing off 90% of var self = this occurrences.
  4. ... not going to continue, speaking to the choir here.

If there are grammar or aesthetic issues involved, I can (ahem) yield on that... but I'm struggling with the conceptual discriminating between what arrow functions are or aren't for.

# Brendan Eich (12 years ago)

Alex Russell wrote:

No.

So "No" to all? You want arguments and yield in arrow functions working on the immediately enclosing arrow?

Whatever the await answer, we need a systematic approach or rule of thumb for the other forms.

I'll continue to point out that the confusion is largely a product of language lawyering, not real-world expectations of JS behavior. Banning await here is only a solution if you're also not going to ban an async descriptor on the arrow expression. THAT is the mistake.

Lawyer Alex, esq, could you please answer the question of what arguments in an arrow should do? yield in an arrow?

# Kevin Smith (12 years ago)
  1. Non-mapping iteration (e.g., forEach())
  2. Lightweight callbacks

Or, mappings to void.

  1. Killing off 90% of var self = this occurrences.

Here's is the iffy part. While getting rid of explicit this-binding is definitely a good thing, we want to make sure that goal fits in with the design at large.

And, previously I meant to say that I'm trying to understand the way the language fits together. Not that I actually do understand. I'll be lucky if I can understand what my two-year old is screaming at me later tonight, let alone a complex evolving language.

# Brendan Eich (12 years ago)

Mark S. Miller wrote:

Putting aside my immediate esthetic reaction, I suggest we consider one of

=>, *=>, !=>

We went over these at the last TC39 meeting. The *=> and !=> forms are future-hostile to elided empty parameter list for arrow.

=>, =*>, =!>

We didn't consider these carefully, just kind of threw up in our mouths a little. Probably we should consider again.

=>, *>, !>

Losing the = hurts, and starts getting too line-noisy.

# Brendan Eich (12 years ago)

Kevin Smith wrote:

1) Non-mapping iteration (e.g., `forEach()`)
2) Lightweight callbacks

Or, mappings to void.

Good point -- forEach is an explicit and more efficient map with garbage result full of undefineds.

3) Killing off 90% of `var self = this` occurrences.

Here's is the iffy part. While getting rid of explicit this-binding is definitely a good thing, we want to make sure that goal fits in with the design at large.

Your quantitative study helped here.

# Mark S. Miller (12 years ago)

My aesthetic judgement is that "!" is not a good choice because grawl is bad for beginners.

Keep in mind that infix ! is proposed strawman:concurrency for use in ES7 to mean "do this operation eventually", e.g.,

p2 = p1 ! foo(a, b);

would be equivalent to

p2 = Promise.cast(p1).send("foo", a, b);

which, for local promises, is equivalent to the ES6

p2 = Promise.cast(p1).then(r => r.foo(a, b));

The use of infix ! here is based on its similar use for concurrent or async message sending in many process algebra languages as well as Erlang and Scala. This proposal is far from accepted, and not relevant prior to ES7. But given that we do adopt infix ! for mean "do this asynchronously/eventually", it would unduly increase the grawlixness to adopt an unrelated symbol, such as ~>, for async arrows.

Generators, on the other hand, are a way of implementing a sequence. As such, they don't fit into that "arrow" niche.

Actually, they do. Thanks to Domenic for triggering the following thought:

Where

x => t

implements the mapping type X -> T, the corresponding

x =*> t

implements the mapping type X -> T*, where the * here reads like the kleene (or regexp) *, i.e., this function maps from an X to zero or more Ts.

given async functions and async arrows, what use cases are left for generator arrows?

Concise synchronous generators.

# Mark S. Miller (12 years ago)

On Thu, Dec 12, 2013 at 2:46 PM, Brendan Eich <brendan at mozilla.com> wrote:

Mark S. Miller wrote:

Putting aside my immediate esthetic reaction, I suggest we consider one of

=>, *=>, !=>

We went over these at the last TC39 meeting. The *=> and !=> forms are future-hostile to elided empty parameter list for arrow.

You're right. I forgot that problem. OTOH, none of this will happen prior to ES7, and by then we might decide to give up on eliding the empty parens. In any case, my other two suggestions stand. I don't like the look either, but they may be worth it for the brevity for an expected-to-be-common case.

# John Barton (12 years ago)

On Thu, Dec 12, 2013 at 11:53 AM, Mark S. Miller <erights at google.com> wrote:

Keep in mind that infix ! is proposed strawman:concurrency for use in ES7 to mean "do this operation eventually", e.g.,

p2 = p1 ! foo(a, b);

Please do not do this ;-)

# Kevin Smith (12 years ago)
p2 = p1 ! foo(a, b);

would be equivalent to

p2 = Promise.cast(p1).send("foo", a, b);

which, for local promises, is equivalent to the ES6

p2 = Promise.cast(p1).then(r => r.foo(a, b));

Yes, but "!" isn't agreed upon. By that time, we might be over our CoffeeScript-inspiration long enough to consider thin arrows. After all, thin arrows are used for a different kind of indirection in C. Maybe...

Actually, they do. Thanks to Domenic for triggering the following thought:

Where

x => t

implements the mapping type X -> T, the corresponding

x =*> t

implements the mapping type X -> T*, where the "*" here reads like the kleene (or regexp) *, i.e., this function maps from an X to zero or more Ts.

Exactly. That's my point, essentially. The design niche for arrows is expressing simple surjections (with side-effects of course), not one-to-many relationships.

Concise synchronous generators.

Restating the feature isn't exactly demonstrating a use case : ) Is there a concrete example which would give us some idea of the benefit of arrow generators?

It's difficult because we don't have much data yet. But that lack of data is another argument for reserving await, I think.

# Domenic Denicola (12 years ago)

I believe this would be a reasonable concise synchronous generator someone could write:

const concat = (a, b) =*> { yield* a; yield* b; }
# Kevin Smith (12 years ago)

Thanks. For this particular example, it's no big deal to use longhand:

function* concat(a, b) { yield* a; yield* b; }

But you could throw a couple of thiss in there and drive the argument that way. But are we going to see such an example pop up enough to justify the feature? I suspect not, but we don't have any data. Better to leave all options on the table, including using "=>" for async arrows. Hence reserving await.

# Domenic Denicola (12 years ago)

Well, let's use it in context then. I think this is correct?

const flattened = iterableReduce(iterableOfIterables, (a, b) =*> { yield* a; yield* b; }, []);
# Claus Reinke (12 years ago)
  1. ... functions "express" mappings
  2. Generators "express" sequences
  3. Don't muddy the waters

If only!-(

In current ES6 design, functions are mixed with generators (probably based on the notion that the generator function call is suspended, even though, in reality, generator functions return generator objects that can be prompted to return next results).

In current ES6 design, generators represent iterators, which are ephemeral pointers into stateful sequences (calling next has a side-effect; previous sequence pointers are invalidated, you need to make copies manually).

waters are clearly muddy :-(

PS. my standard suggestion for water purification:

  • have a syntax for generator blocks, not generator functions do*{...} equivalent to immediately applied generator function, (function*(){...}())

  • have side-effect-free iterators

  • combine modular language features at will, eg: x=>do*{ yield 1; yield 2 } function(x) { return do*{ yield 1; yield 2 } }

  • profit! (simpler, more modular semantics, better compositionality)

# Kevin Smith (12 years ago)

Well, let's use it in context then. I think this is correct?

const flattened = iterableReduce(iterableOfIterables, (a, b) =*> { yield* a; yield* b; }, []);

Makes sense to me.

Still, what's the alternative?

function* concat(a, b) { yield* a; yield* b; }
const flattened = iterableReduce(iterableOfIterables, concat, []);

Not so bad. Again though, we can't make good optimization choices in a (data) vacuum.

# Rick Waldron (12 years ago)
p2 = p1 ! foo(a, b);

Please do not do this ;-)

John, I agree with you on this and have expressed my concern in the past.

I still contend that using ! to indicate some async characteristic just reads contradictorily if we consider that ! as an exclamation is also used to indicate urgency or immediacy—which are absolutely the opposite of "async". Of course, it could be argued that using ! to indicate logical negation doesn't match its written punctuation meaning either, but that case is unrelated to both the original meaning and the opposite of the original meaning (the same arguments are true for its factorial symbol, ML, Scheme, Ruby and Haskell meanings).

The concurrency strawman proposes ! as an infix operator, and gives this as a simple example:

function add(x, y) { return x + y; }
const sumP = add ! (3, 5); //sumP resolves in a later turn to 8.

I suppose this isn't so bad, but what about this:

!isComparable!(a, b);

I don't think it would be hard to explain this, it's just (subjectively) strange to see. Maybe I'll get over it?

The requisite "[No LineTerminator here]" imposed by "!" (as an infix operator) creates a restriction that will fail silently:

function f(n){ return n; }
f
(1); // 1
f
!(2); // false (but probably expected 2)

Which seems like the kind of issue that the committee has balked at in the past.

# John Barton (12 years ago)

offline Mark lured me in to making more suggestions. I bit:

p1 >< foo(a,b);  // p1 "eventually when the sand runs out" foo(a,b);

p1 <> foo(a,b); // Because a wikipedia page has diamond for temporal logic 'eventually'.

I guess angle brackets in general are trouble however, I recall some issues with html and JS.

// Non-uniform tokens, maybe "modified tokens", where | isn't an operator but is part of new tokens:
p1|.foo(a,b);  // can't we have bar dot ?
p1|['foo'](a,b);  // Here we have |[ ... ]
p1.foo|(a,b);  // I'm just substituting | for ! in the examples in [0].

p1~.foo(a,b);  // not 'now'
p1~['foo'](a,b);  // Here we have ~[ ... ]
p1.foo~(a,b);  // I'm just subst ~ for ! in the [0] examples.

Yes, ~ and | have meanings, but ! is so so much more common.

# Brendan Eich (12 years ago)

I guess angle brackets in general are trouble however, I recall some issues with html and JS.

Probably best to avoid <>, although in HTML, only </ followed by certain letters would be a problem ;-).

Yes, ~ and | have meanings, but ! is so so much more common.

I want to point out this example of sweetjs.org -- a sweet.js macro for let async:

gist.github.com/disnet/7934003 (contents below)

Sweet.js has come a long way. This makes me wonder just how much built-in async syntax we need to hardcode into ES7.

/be


let let = macro {
    rule { async $vars  ... = $fname ... ($params ...); $rest ...} => {
        $fname ... ($params ..., function (err, $vars ...) { 
            if (err) throw err;
            $rest ... 
        })
    }
}
 
var buffer = new Buffer(1024);
 
let async infile = fs.open("/tmp/in.txt");
let async outfile = fs.open("/tmp/out.txt");
let async bytesRead, buffer = fs.read(infile, buffer, 0, 1024, 0);
let async bytesWritten, buffer = fs.write(outfile, buffer, 0, 1024, 0);
console.log("We read " + bytesRead + " bytes and wrote " + bytesWritten + " bytes.");
 
 
// compiles to:
 
var buffer$188 = new Buffer(1024);
fs.open('/tmp/in.txt', function (err$190, infile$191) {
    if (err$190)
        throw err$190;
    fs.open('/tmp/out.txt', function (err$193, outfile$194) {
        if (err$193)
            throw err$193;
        fs.read(infile$191, buffer$188, 0, 1024, 0, function (err$196, bytesRead$197, buffer$188) {
            if (err$196)
                throw err$196;
            fs.write(outfile$194, buffer$188, 0, 1024, 0, function (err$200, bytesWritten$201, buffer$188) {
                if (err$200)
                    throw err$200;
                console.log('We read ' + bytesRead$197 + ' bytes and wrote ' + bytesWritten$201 + ' bytes.');
            });
        });
    });
});
# Brendan Eich (12 years ago)

duplicate message

# Tab Atkins Jr. (12 years ago)

Probably best to avoid <>, although in HTML, only </ followed by certain letters would be a problem ;-).

Yeah, nothing wrong with <> inside of <script> blocks. So long as we never define a </script> operator we're okay.

# Brendan Eich (12 years ago)

But I agree with John: <> and >< have "issues", not just in context of HTML, but also there -- readability/grawlix/overloaded-character at least.