Skip to content

Latest commit

 

History

History
116 lines (63 loc) · 12.3 KB

08-24.md

File metadata and controls

116 lines (63 loc) · 12.3 KB

August 24, 2020 Incubator Call Notes

Attendees:

  • Jack Works (JWK)
  • John Hax (JHX)
  • Jason Orendorff (JTO)
  • Shu-yu Guo (SYG)
  • Chengzhong Wu (CZW)
  • Mark Cohen (MPC)

SYG: Proposal is to add syntax for each Promise combinator await.all, await.allSettled, etc. Idea is this syntax metaproperty is shorthand for calling the underlying Promise combinator. Most compelling argument I’ve heard in plenary was there was performance and concurrency left on the table because folks are using serial await without discovering or understanding the Promise combinators.

SYG: I’m skeptical about that mainly because nothing is usually wrong if you do it serially, but if you make the default / easier-to-reach-for thing concurrency, then - maybe I’m wrong because I’m not a web dev but it seems like it’d be better to err on the side of correctness instead of concurrency. Champions, could you address this?

JWK: The main use case is handling a set of Promises. I think it might not be enough to handle a set of async data. I have an issue in the repo - I had an idea of adding these await operations into the for await of syntax. In that case it will greatly improve how we handle async iteration. Today you have to do a map on the array, and pass an async function into the map, then you have to await Promise.all and do your stuff, that’s not so ergonomic. Code sample today: await Promise.all(array.map(async () => do some thing)) Code with this proposal: await.all array.map(async () => do some thing) Hypothetical future (not included in this proposal yet, but there’s an issue open for it): for await.all (const x of array) ...

SYG: What does the third line do? It would execute the body, gather it all up, and then await all of it?

JWK: It’s a shortcut for the second line.

SYG: So no matter how many things are in the array you’re awaiting the final combined Promise.

JWK: Yes. I think the third line might be too complex to add in this proposal, since I’m not sure how the third line should work, and the second line is already such a big improvement than the first.

SYG: I’m sorry for being so pushy, but I’m not seeing what the big improvement is from the first line to the second other than not typing Promise..

JWK: In my development, if I want to do this, I need to first type array.map(async () {}) something, and then I have to move my cursor back to the start of the line to add await, and I have to wrap the expression in the Promise.all(), that cause multiple cursor jump around to finally get the code. If we choose the second line, we only need to move the cursor back once and type await.all.

SYG: So await as a separate keyword will still exist aside from await.all, but the idea is that since it starts with await, you’ll be more likely to remember to type await.all? Why is it today that you don’t remember to type await Promise.all?

JWK: I need to type the close parentheses at the end of the line too - I first need to move to the start of the line and type await Promise.all(, and then move to the end of the line and type ). In the second form there’s only one jump to the start of the line.

SYG: What would you say the split is between the four different promise combinators? Are you looking for all almost all the time? Are you looking for the other ones ever?

JWK: I only use Promise.all in my daily life, but JHD says await* doesn’t match yield* well. And await* only represents Promise.all. So he thinks the four combinators should each have a syntax form for consistency. On the internet people are always asking about Promise.all, and the other three are used less consistently. But there are still use cases for those three.

SYG: My personal takeaway so far is that I’m not really convinced about the other combinators other than all. If all is used almost all the time, if we do some special syntax for it, then maybe just doing it for all is fine, but I’m still trying to better understand the cursor jumping case. It’s not clear how to apply this thinking to other features - is the idea that we’re waiting for something to jump out at us over and over that’s annoying and then we’ll add a shorthand for it? I’m not sure what motivates this other than - not to discredit this - I’m not sure this is quite enough. Part of this could be solved by a sophisticated editor; there are snippet managers and stuff.

JWK: I posted a link tc39/proposal-await.ops#10 to the for await case.

SYG: So part of this is the actual improvement with the for loop syntax.

JWK: Yes. I think we should improve the concurrency of the for of statement.

SYG: So if I understood your previous statement correctly, you’d be awaiting the final promise, not each one? It’s like await.all for not for await.all?

JWK: Yes, The main idea is I want to concurrently do a set of things and it might have await in the middle. I want the for loop to execute concurrently if it has await in it. But I think it’s a big change to the control flow.

MPC: My initial reaction was similar to SYG, but possibly a better solution is to do research along the lines of what FHS and YSV presented at the most recent plenary--see if that research bore out that this would be a significant improvement, in terms of conceptual reachability.

SYG: I think that would be more convincing than now. Currently, this moves the needle relatively little: you want to type less and move your cursor less. But you’ve talked about a world you want to get to which is not just avoiding typing Promise.all, but also avoiding typing array.map, avoiding making these async closures. So the actionable feedback I’d give is that it’s maybe a more ambitious proposal, but if that’s the issue you care about solving, then I’d pursue that, because I think people agree it’s not super compelling to give any, allSettled, and race the same treatment as all. I disagree with JHD’s consistency feedback that we should have these for all combinators - I think it doesn’t make sense to reflect all library functions in syntax.

JWK: What do you think of await*?

SYG: I don’t mind it. I don’t have much to say other than I’m not sure it bothers me that it doesn’t match yield*, but maybe I’d have to write some programs to see if it does bother me. Could you elaborate on the inconsistency?

JWK: I don’t think it’s inconsistent, but JHD does. In fact I think it’s very consistent.

JHX: I can’t say it’s inconsistent, but yield* will return a single value not an array. I’m not sure if this is consistent or inconsistent.

SYG: I kind of see it as mirrors.

JWK: I’m not sure how JHD thinks of it. In my view, they’re all handling iterables.

SYG: I see it as consistent. It’s turning this iterable thing into control flow, right? It’s not like it yields an array.

JTO: That’s what it does, but there’s a wrinkle to this that yield* is a parallel to normal function calls, in that you go into the body of the generator, run it, and then return the value back out. That sounds like regular await to me, if you were to translate that concept to async land. It allows you to do the refactoring of factoring some code out of a generator into a separate generator, that’s what yield* does. The corresponding thing if you have an async function and you want to factor out some code, that’s just await.

SYG: JWK, you’re just proposing await* as await.all?

JWK: Not sure, the original idea of await* is on discourse.

SYG: Are you envisioning that if await.all were the syntax it would better compose with loops that you want to use?

JWK: I don’t think it helps much in the for of case.

SYG: My personal recommendation stays the same, which is the actual use-case you have of the whole thing - looping over things, having to construct intermediate arrays and async closure - is a bigger ergonomics issue than typing Promise.all, and you should try to solve that directly. If you think the existing proposal is by itself enough of a benefit to still have it in the language, then of course you can bring that back to committee. I personally don’t think it’s worth it but I’m happy to be convinced, and I was trying to get more compelling use cases last time.

JWK: I have an intuition that the semantics for concurrent for of might be very complex or maybe a footgun.

SYG: Interesting. That’s another route that you could go down. One route is what MPC suggested - do some more qualitative research on the ergonomics of this. The other is to have another proposal for the for await.all thing, and then a possible outcome of that might be that it’s so complex and not beneficial, and then that complexity justifies this simpler proposal. I got the sense on IRC last time that there were many folks like TAB who did really like the current proposal. I’m happy to read the room again if you bring it again as-is. I’m not planning to block. On the one hand it doesn’t seem like this syntax weight is worth it, on the other hand it’s very little weight that you’re asking for.

JWK: Last time on IRC I discussed about the concurrent for loop, and I don’t remember who, but someone said that allowing programmers to write concurrent loops will be abused and lead to race conditions.

SYG: That’s part of the counter-argument I was making to the argument that people are leaving concurrency on the table; if you make a concurrent for loop very easy to write, does that lead to more bugs than if people have to jump through some hoops to get concurrency. But I don’t know how to weigh these arguments, I’ve worked in languages that make it easy to shoot yourself in the foot, and did that increase bugs? I don’t know. There’s still an argument about how easy it should be to write concurrency.

MPC: I think that should be researched under the “cognitive dimensions of notation” framework as well. Those are two qualitative arguments that are difficult to weigh.

SYG: To push back a little bit, I welcome this research, but it is a new thing that FHS and YSV are proposing, so it’s not a requirement or a bar that we’re holding proposals to.

JWK: How can we make this design into a number that we can compare? Do we need a questionnaire that we can spread around and ask people to rate each design?

MPC: I’m not an expert on this framework - I’d suggest looking at the presentation from last plenary. And I’m sure if you reach out to either FHS or YSV they’ll be happy to answer those questions.

JTO: New topic. Is any new syntax that we add going to be easy to read and understand? I understand the problem that existing combinators and async code can be really weird and hard to decode. It’s hard to write, maybe hard to maintain. But what it does have going for it is that it’s made of parts that people can read and understand. Once you understand what Promise.all does, you can decode code that uses it. Does await.all make it easier? Or does some new for await of form need to be memorized and mentally translated? In which case it’s just an extra step. I think it’s possible that we go down this route and find that we’re not helping programmers.

JWK: await* might not be easy to understand, but await.all I think it’s clear.

JTO: await.all is clear, my issue is with the for await of construct. SYG had to ask what it means, I still don’t understand what it means. It’s hard for me to give feedback on this proposal because it doesn’t seem complete. If we take it as it currently stands, my position is similar to SYG which is that new syntax which is equivalent to an existing library function seems not worth it. But the point I’m making now is, what if we’re trying to solve this deeper problem with the for loop? I’m cautious about that too because if it’s not easy to get an understanding of what the new syntax does, it’s possible we could add features that make code look nice without helping programmers write good code.

JWK: Maybe I will try to think about what the concurrent for loop should do, and then we can discuss it.

SYG: I’d be interested to hear that.

JTO: Me too.

MPC: Me three.

Conclusion (in my(JWK) view): Do research on the current proposal by the framework that Yulia presented. Investigate semantics of concurrent for loop