- BSH: Bradford Smith (Google)
- JHD: Jordan Harband (Coinbase)
- JHX: Hax (invited expert)
- JRL: Justin Ridgewell (Google)
- JSC: J. S. Choi (Indiana University)
- Liu Tao (Bytedance)
- RBN: Ron Buckton (Microsoft)
- RGN: Richard Gibson (Agoric)
- RKG: Ross Kirsling (PlayStation)
- SHO: Sarah “GHP” Groff Hennigh-Palermo (Igalia)
- TAB: Tab Atkins-Bittner (Google)
- WH: Waldemar Horwat (Google)
- WMS: Willian Martins (Netflix)
This ad-hoc meeting was a result of overflow of discussion during the plenary meeting on January 26, 2022, based on these two articles:
That discussion was about the following proposals:
- The pipe operator
… |> …
(aka “Hack pipes”) - Function.pipe (a function version of the F# pipe operator)
- The bind-this operator
…::…
(and its variant call-this…@(…)
) - The Extensions syntaxes
…::…
,…::…:…
, andconst ::{ … } = …;
- Partial function application
…~(…)
(aka “PFA syntax”)
For background about the history of the pipe-operator proposal (in which Hack pipes and an alternative approach—F# pipes—were compared in favor of Hack pipes), see that proposal’s history document.
JSC: JHX says that he needs to leave early, so we will prioritize his remaining topics from yesterday’s plenary meeting.
JHX: “Overlap doesn’t mean all proposals have the same goal and use cases.” I wrote an article about my thoughts. PFA (partial function application) syntax can be used with any proposal, including F# style. PFA syntax can also solve bind-this. Extensions are a version of a feature that is in most other important programming languages [a list is in the repository explainer], and it may be able to solve other features such as first-class protocols, safe invoking, transpilation, tree shaking, and custom operators. Although Extensions can be used to solve linear dataflow, it is more general than that and solves many other problems, and it may give the Committee more freedom to fix them.
JHX: My next point says, “What syntax sugar we want, what the tasks they optimize for?” Hack-style pipes are a general solution for dataflow. But is generality good? At the same time, we do not want to have people change their coding style, which I think call-this and Function.unThis [demethodize] would do. See Lodash and Underscore for this kind of flexibility [in this
-receiver versus first argument].
JHX: And, in my opinion, the Hack style is a mix of a pipe operator, topic variable, and the ability to escape function boundaries. These features are not necessarily linked together.
WH: I think Extensions are confusing and have a net negative value. They use the same syntax to sometimes pass a first argument first and sometimes a this
-receiver first; sometimes they call functions, sometimes they decode property descriptors, all with identical syntax—the syntax is overloaded to do multiple things.
JHX: I will try to explain more how Extensions could help developers in the next plenary meeting.
JSC: I’m a little confused by your saying that Hack pipes combine unrelated features like “escaping function boundaries”. But you can clarify that later. More to the point: doesn’t the criticism that Hack pipes are too general also apply to Extensions, which are also very general and try to solve many cross-cutting problems?
JHX: It’s different because Extensions focus on methods, and methods are the core feature of the language. Also, Hack pipes don’t have precedent except in Hack; Hack pipes are a new thing, and they’re unproven.
JSC: Hack-style pipes are also in some other languages; I recall that we have done a review of this before. Also, Function.pipe would already bring F# pipes, just in a metafunction but without a syntax operator.
JHD: And inventing [new] stuff [that is not already in other languages] is a good thing sometimes.
JRL: The F# pipe operator [a syntactic version of Function.pipe] will never pass plenary. We can’t revisit them. Revisiting them would kill the possibility of any pipe operator.
JHX: It’s not good that we can’t revisit the F# pipe operator. See my article. But we need to help functional programmers.
JRL: I’m a maintainer of Lodash and Underscore. The functional-programming (FP) variants of these are both barely used and also observably slower because of their extra closure allocations. People don’t use them because they were slower.
JHX: I acknowledge that performance problem.
JRL: Solving functional programming isn’t the problem that pipes should solve. And most things that FP needs, except for curried functions, are indeed solved by Hack pipes.
WH: JRL, what do people use instead of the FP versions [in Lodash]?
JRL: The regular versions of Lodash’s functions.
JHX: Users of Lodash don’t want FP; the people we should design pipelines for are the people that need to do FP. That’s my reply.
JSC: JHX has to go soon. Next topic [about Extensions], please.
JRL: Extensions accessors break the ability to pipe clearly, like in obj::foo = 1 :: fn()
. The contexts in this
expression’s steps are unclear.
JHX: The ::
operator there is the same as .
. (obj::foo) = (1 :: fn())
.
JRL: That’s not what I mean. If you use accessors in the middle of a pipeline, you’ve broken the pipeline. It’s not clear what would follow the accessor.
JHX: Yes, if you use accessors, it breaks the pipeline. If you want a real pipeline, you should use a dedicated pipe method, like obj::pipe(…)
.
TAB: It’s just like method chaining.
JHX: I have to go. I’ll present an update about Extensions at the next plenary meeting.
WH: [next topic] Thanks to JSC for the chart! It’s immensely helpful in focusing and clarifying the discussions.
RGN: Also thanks for the chart, on the record.
JRL: I want to advocate for the pipe operator solving real problems. I don’t want to get distracted from adding the pipe operator. The big use case I envision is demonstrated by the new FireBase API, which switches from object-oriented (OOP) methods (which only one minifier today, Google Closure Compiler, is advanced enough to minify because it is both a type system and a minifier) to separately importable, tree-shakable functions. OOP gives a nice fluent API but fails to be minified by majority minifiers. We need to be able to statically minify free functions that are usable with a pipe-like fluent interface.
JHD: The old [abandoned] bind operator (and [the new] bind-this) would have solved this too for OOP.
JRL: Yes, but Hack pipes solve this too, even if it’s slightly less nice. Hack pipes still solve the big use case I care about, bundle sizes, by passing the “this
context” as the first param.
JSC: I recall that FireBase’s new API was something that helped persuade Shu-yu Guo (SYG) that a pipe operator might be useful.
SHO: Wholeheartedly agree about the use of this
—from recent adventures in front-end userland and would hate to see the pipe operator get bogged down. There’s a reason why a pipe operator is requested so often by the community. I’m concerned about bind-this—JHD isn’t wrong: it can be used to chain dataflow. But there’s a reason why developers don’t like this
; its dynamic binding is complicated. Hack pipes leverage [static lexical bindings].
JHD: To clarify, I am fully supportive of Hack pipes. But I am also supportive of bind-this, which I truly want, and whose order I prefer to call-this.
JSC: Question to everyone. How bad is it that Hack pipes and bind-this overlap?
JHD: I think their overlap should not be a concern. TMTOWTDI (There’s More Than One Way To Do It) has been a principle of JavaScript by Brendan Eich since its beginning, although I personally also like TOOWTDI (There’s Only One Way To Do It) as a principle. I’ve seen TAB’s concerns about ecosystem forking (between functions designed for this
-receivers versus functions that do not), but the ecosystem-forking concern is separate
WH: The answer to whether multiple ways or syntaxes of doing something are harmful critically depends on the duplication’s effect on APIs and how viral it is.
WH: Suppose we’re considering having two syntaxes 𝘟 and 𝘠 to use APIs. If module or person 𝘈 uses syntax 𝘟 which interoperates better with syntax 𝘟 than syntax 𝘠 and that pressures module or person B to use syntax 𝘟 in their new APIs to interoperate with person 𝘈’s APIs, that virality encourages ecosystem forking and API wars. Introducing multiple such ways into the language is bad. On the other hand, if person 𝘈’s choice of syntax has no effect on person 𝘉 and they can interoperate without any hassles, then that’s generally benign.
JHD: I agree—but I would just add that .
method calls already exist and, even if they are a mistake, they are a pattern that needs to be lubricated.
WH: Mark Miller (MM) isn’t here, but he also has opinions on this question.
SHO: I don’t love bind-this and I share WH’s ecosystem-forking concerns. But, outside that preference, the overlap only concerns me if it derails the pipe operator.
TAB: I’m behind TMTOWTDI, but yes, virality of a schism isn’t great, and JRL explicitly said he wants to create this
-based APIs. I won’t shed too many tears if both pipe operator and bind-this show up, but I don’t think it would be a great thing if that causes libraries to have to choose which API style they have to use. It’s a moderate problem but not one I would lose too much sleep over.
JRL: I don’t see that effect as any worse than not knowing the order of parameters. There’s the small chance of a schism but it wouldn’t be that bad. Whether it uses this
or context-first is just a part of being familiar with the functions you’re using. Which you still have to be familiar with, because you need to know the order of parameters; whether this
is used is just a part of that. Hack doesn’t solve it because it only provides a convention that the first param is the context, but the rest of the params still have to be known by the dev.
TAB: I don’t think that’s right. It makes a big difference in the calling expression syntax.
JHD: If you want to use a function with a this
receiver, with the pipe operator alone, you have to use a function-call expression, not a method-call-like expression. I don’t think call-this + Hack pipes would be pleasant or fluent enough to justify call-this.
JRL: I agree. I want object-oriented programmers to use less prototype-based .
method calls (which are not tree shakable) and more tree-shakable things. I fear that if we don’t have a proposal that achieves a great fluent API, people will continue to use OOP methods because they are completely fluent and nice to use. That includes things like patching the prototypes to add methods, e.g., Array.prototype.unique or groupBy. Hack pipes solve most of that, but because the topic reference still needs to be used (it's not tacit), it’s not a 100% fluent API.
TAB: That is indeed solved by the Hack pipe operator, just slightly longer.
JRL: I agree. I would be happy if we only got the pipe operator. But I fear that it wouldn’t be concise enough to encourage OOP developers to use it. And I dislike that we may block bind-this because we don’t want both to exist.
JHD: Back then, there was a rumor that the old bind operator (which did four things) would kill the pipe operator (which does two or three of them). As I have said in the past, I will block the pipe operator if neither bind-this nor call-this will advance. [JHD proceeds to clarify that either bind-this or call-this would be acceptable, although JHD prefers bind-this to call-this.]
JRL: I am not as hardline. I want a pipe operator more than bind-this (it’s more versatile and useful), but I want bind-this after.
WH: What is “call-this”?
JHD: An alternative proposal to bind-this from TAB. f.@(receiver, arg0, arg1, …)
being syntactic sugar for f.call(receiver, arg0, arg1, …)
.
WH: Regarding bind-this, I am concerned that adding another way to call methods like .
would be confusing, especially if both syntaxes look similar and become common. People will write Abracadabra.shazam()
when they mean Abracadabra::shazam()
or vice versa. On the other hand, call-this seems fairly innocuous.
JHD: ?.
(optional chaining) didn’t add much confusion.
RKG: I have expressed that same concern in the past. There’s little precedent for ::
with these semantics in other languages. I am concerned about beginners having to learn about when to choose .
versus ::
.
JHD: PHP’s ::
and Ruby’s ::
are similar to bind-this.
[more discussion about potential for confusion between ::
(in bind-this in or Extensions) – versus regular method calls (or property access) with .
]
JRL: I’m hearing two concerns, first about unfamiliarity with syntax and second about confusion between a lexical variable and a property access. For the first, I think that applies to the pipeline operator as well, having an operator that flips the flow of data requires the dev to be familiar with the operators. As for the second, I think the use of linters and/or TypeScript may make the confusion less of an issue while people are learning. They’ll figure out that an expression isn’t a property access when the linter warns that there’s no variable foo
in scope.
JSC: We’ve been talkings about a lot of details. Let’s go back to a high-level view and make sure we don’t miss Function.pipe and PFA syntax. [reviews overall diagram again]
JHD: Function.pipe isn’t controversial. Although I personally feel that it doesn’t belong in the core language, I wouldn’t block it. Extensions and PFA syntax are both highly polarizing.
WH: I am skeptical about Extensions for reasons I mentioned earlier. Bind-this might be okay. Like Mark Miller (MM), I am also concerned about the complexity cost of the sheer amount of these—if we adopted PFA syntax, bind-this, Hack pipes, and Function.pipe, that would be too many.
JRL: I like PFA syntax and would use it, but I don’t think it solves a critical language need. Hack pipes do solve a critical language need. I would be happy if we tabled PFA syntax until after the pipe operator.
RBN: After F# pipes were dropped, I haven’t pushed PFA syntax, but I plan to push it later.
TAB: I think PFA syntax covers an important use case: method extraction from an owner as a bound function [e.g., buttonEl.addEventListener('click', obj => obj.method())
, buttonEl.addEventListener('click', obj.method.bind(obj))
or—with PFA syntax—buttonEl.addEventListener('click', obj.method~())
]. That is also one of the three use cases of bind-this [e.g., buttonEl.addEventListener(obj::obj.method)
]. That’s why I made the call-this proposal, which, unlike bind-this, does not cover that method-extraction use case [redundantly with PFA syntax].
JSC: We have run out of time. Thank you everyone for coming. Overall conclusions include:
- In general, some overlap is okay, but too much is bad; we have to decide this on a case-by-case basis.
- We reaffirmed that F# pipes are off the table.
- We reaffirmed that it would be okay to have both Hack pipes and either bind-this or call-this (although Mark Miller [MM], who expressed possible disagreement with this at plenary yesterday, is not present at this current meeting).
- JHD reaffirmed that he would block Hack pipes if neither bind-this, call-this, nor any-other-syntax-that-replaces-this-receivers is able to advance.
- Nobody present has any strong opinions regarding bind-this versus call-this; they have different moderate tradeoffs with regards to familiarity, fluency, and risk of ecosystem schism. Call-this can be considered a bikeshed alternative style of bind-this, so we will follow up about bind-this versus call-this in the bind-this bikeshedding issue.
- Extensions and bind-this/call-this are still mutually exclusive.
- PFA syntax and Extensions continue to polarize the Committee.
- RBN (PFA syntax’s champion) is waiting for the further advancement of Hack pipes until trying PFA syntax again.
- JHX (Extensions’ champion) will present an update on Extensions again at the next plenary in an effort to increase Committee support.
- The overlap between pipe operator and Function.pipe is okay.
JSC: Does anyone object to these conclusions? [silence] Sounds good. Thank you, everyone.