├── .editorconfig
├── .github
├── pull_request_template.md
└── workflows
│ ├── floodgate.yml
│ ├── publish.yml
│ └── stale-lock.yml
├── .gitignore
├── .travis.yml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── HISTORY.md
├── LICENSE.md
├── README.md
├── package-lock.json
├── package.json
└── spec.html
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | insert_final_newline = true
6 | trim_trailing_whitespace = true
7 | charset = utf-8
8 | indent_style = space
9 | indent_size = 2
10 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | Thank you for taking the time to contribute to this proposal!
2 | We’re so happy you’re helping out.
3 |
4 | 1. Please take a look at the [contributing guidelines][]
5 | and the resources to which it links.
6 | 2. Please include the purpose of the pull request. For example:
7 | * “This adds…”
8 | * “This simplifies…”
9 | * “This fixes…”
10 | 3. Please be explicit about what feedback, if any, you want:
11 | a quick pair of eyes, discussion or critique of its approach,
12 | a review of its copywriting, and so on.
13 | 4. Please mark the pull request as a Draft if it is still unfinished.
14 |
15 | All text in this repository is under the
16 | [same BSD license as Ecma-262][LICENSE.md].
17 | As is the norm in open source, by contributing to this GitHub repository,
18 | you are licensing your contribution under the same license,
19 | as per the
20 | [GitHub terms of service][ToS].
21 |
22 | [contributing guidelines]: https://github.com/tc39/proposal-pipeline-operator/blob/main/CONTRIBUTING.md
23 | [LICENSE.md]: https://github.com/tc39/proposal-pipeline-operator/blob/main/LICENSE.md
24 | [ToS]: https://help.github.com/en/github/site-policy/github-terms-of-service
25 |
--------------------------------------------------------------------------------
/.github/workflows/floodgate.yml:
--------------------------------------------------------------------------------
1 | name: Comment Floodgate
2 | on: issue_comment
3 | jobs:
4 | floodgate:
5 | runs-on: ubuntu-latest
6 | steps:
7 | - uses: js-choi/github-comment-floodgate@v2
8 | with:
9 | minutes-in-period: 60
10 | # This number is how many comments are allowed per period per issue.
11 | max-comments-per-period: 12
12 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Build and Deploy to GitHub Pages
2 | on:
3 | push:
4 | branches:
5 | - main
6 | workflow_dispatch:
7 | jobs:
8 | build-and-deploy:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Checkout 🛎️
12 | uses: actions/checkout@v2
13 | with:
14 | persist-credentials: false
15 |
16 | - name: Setup Node
17 | uses: actions/setup-node@v1
18 | with:
19 | node-version: '14.x'
20 |
21 | - name: Install and Build 🔧
22 | run: |
23 | npm install
24 | npm run build
25 |
26 | - name: Deploy 🚀
27 | uses: peaceiris/actions-gh-pages@v3
28 | with:
29 | github_token: ${{ secrets.GITHUB_TOKEN }}
30 | publish_dir: ./dist
31 |
--------------------------------------------------------------------------------
/.github/workflows/stale-lock.yml:
--------------------------------------------------------------------------------
1 | name: Lock Stale Threads
2 |
3 | on:
4 | schedule:
5 | - cron: '0 0 * * *'
6 |
7 | workflow_dispatch:
8 |
9 | permissions:
10 | issues: write
11 | pull-requests: write
12 |
13 | concurrency:
14 | group: lock
15 |
16 | jobs:
17 | action:
18 | runs-on: ubuntu-latest
19 | steps:
20 | - uses: dessant/lock-threads@v3
21 | with:
22 | issue-inactive-days: 7
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | out
4 | dist
5 | npm-debug.log
6 | deploy_key
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: off
2 |
3 | language: node_js
4 |
5 | node_js:
6 | - "8"
7 |
8 | script:
9 | - bash ./deploy.sh
10 |
11 | env:
12 | global:
13 | - ENCRYPTION_LABEL: "bc1f69dfbe70"
14 | - GH_USER_NAME: "littledan"
15 | - GH_USER_EMAIL: "littledan@igalia.com"
16 | - PRIVATE_KEY_FILE_NAME: "github_deploy_key.enc"
17 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Code of conduct
2 | This repository is a TC39 project, and it therefore subscribes to
3 | its [code of conduct][CoC]. It is available at .
4 |
5 | We all should strive here to be respectful, friendly and patient,
6 | inclusive, considerate, and careful in the words we choose.
7 | When we disagree, we should try to understand why.
8 |
9 | To ask a question or report an issue, please follow the [CoC]’s directions, e.g., emailing
10 | [tc39-conduct-reports@googlegroups.com][].
11 |
12 | More information about contributing is also available in [CONTRIBUTING.md][].
13 |
14 | [CoC]: https://tc39.es/code-of-conduct/
15 | [tc39-conduct-reports@googlegroups.com]: mailto:tc39-conduct-reports@googlegroups.com
16 | [CONTRIBUTING.md]: https://github.com/tc39/proposal-pipeline-operator/blob/main/CONTRIBUTING.md
17 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to the ES pipe operator’s proposal
2 | First off, thank you for taking the time to contribute! 🎉
3 |
4 | Here are some suggestions to contributing to this proposal.
5 | The pipe operator in JavaScript has a [long and twisty history][HISTORY.md].
6 | A lot of issues have already been argued round in round in circles.
7 |
8 | Bearing that in mind, please try to read the following
9 | before making new issues or comments:
10 |
11 | 1. [HISTORY.md][]: This will give a lot of context
12 | behind what’s been happening to the proposal since its creation in 2015.
13 | 2. The general [TC39 Process][], which summarizes
14 | how TC39’s “consensus” and “Stages” work.
15 | 3. The guide on [contributing to TC39 proposals][contributing guide].
16 | 4. The [TC39 Code of Conduct][CoC]:
17 | It has important information about how we’re all expected to act
18 | and what to do when we feel like someone’s conduct does not meet the Code.
19 | We all want to maintain a friendly, productive working environment!
20 | 5. The [TC39 How to Give Feedback][feedback] article.
21 | 6. The [proposal explainer][] to make sure that it is
22 | not already addressed there.
23 | 7. The [TC39 Matrix guide][] (if you want to chat with TC39 members on Matrix,
24 | which is a real-time chat platform).
25 | 8. If the explainer does not already explain your topic adequately,
26 | then please [search the GitHub repository’s issues][issues]
27 | to see if any issues match the topic you had in mind.
28 | This proposal is more than four years old,
29 | and it is likely that the topic has already been raised and thoroughly discussed.
30 |
31 | You can leave a comment on an [existing GitHub issue][issues],
32 | create a new issue (but really do try to [find an existing GitHub issue][issues] first),
33 | or [participate on Matrix][TC39 Matrix guide].
34 |
35 | Please try to keep any existing GitHub issues on their original topic.
36 |
37 | We’ve also installed a [“floodgate” moderation bot](https://github.com/marketplace/actions/comment-floodgate)
38 | (see [issue #231](https://github.com/tc39/proposal-pipeline-operator/issues/231)).
39 | When a issue gets a huge flood of new comments (i.e., new comments are being created at a very high rate),
40 | then the bot will temporarily lock that issue.
41 | (The threshold rate is currently set to twelve messages in the same thread in the same sixty minutes.)
42 | This doesn’t mean that the automatically locked high-traffic threads will get locked permanently.
43 | It just means that the issue’s thread will cool down until a volunteer moderator is able to look at what’s going on.
44 |
45 | If you feel that someone’s conduct is not meeting the [TC39 Code of Conduct][CoC],
46 | whether in this GitHub repository or in a [TC39 Matrix room][TC39 Matrix guide],
47 | then please follow the [Code of Conduct][CoC]’s directions for reporting the violation,
48 | including emailing [tc39-conduct-reports@googlegroups.com][].
49 |
50 | Thank you again for taking the time to contribute!
51 |
52 | [HISTORY.md]: https://github.com/tc39/proposal-pipeline-operator/blob/main/HISTORY.md
53 | [CoC]: https://tc39.es/code-of-conduct/
54 | [TC39 process]: https://tc39.es/process-document/
55 | [contributing guide]: https://github.com/tc39/ecma262/blob/master/CONTRIBUTING.md#new-feature-proposals
56 | [feedback]: https://github.com/tc39/how-we-work/blob/master/feedback.md
57 | [proposal explainer]: https://github.com/tc39/proposal-pipeline-operator/blob/main/README.md
58 | [TC39 Matrix guide]: https://github.com/tc39/how-we-work/blob/master/matrix-guide.md
59 | [issues]: https://github.com/tc39/proposal-pipeline-operator/issues?q=is%3Aissue+
60 | [tc39-conduct-reports@googlegroups.com]: mailto:tc39-conduct-reports@googlegroups.com
61 |
--------------------------------------------------------------------------------
/HISTORY.md:
--------------------------------------------------------------------------------
1 | # Brief history of the JavaScript pipe operator
2 | The pipe operator in JavaScript has a long and twisty history.
3 | Understanding that history can give context behind
4 | what’s been happening to the proposal since its creation in 2015.
5 |
6 | For information on what “Stage 1” and “Stage 2” mean,
7 | read about the [TC39 Process][].
8 |
9 | More information about contributing is also available in [CONTRIBUTING.md][].
10 |
11 | ## 2015–2017
12 | People are debating about a [proposed bind operator `::`][bind]
13 | by [@zenparsing][] (Brave).
14 | A binary `::` operator that binds its left-hand side to its right-hand side (a function)
15 | would serve as a “pipe” operator in JavaScript
16 | (e.g., `a::f(b, c)::g(d, e)` would be a “pipeline” equivalent to
17 | `g.call(f.call(a, b, c), d, e)`).
18 | However, parts of TC39 object to using `this`,
19 | saying that using `this` is strange outside of methods.
20 | Debate online in the `::` repository (and also offline) is bogged down in circles.
21 |
22 | [@littledan][] (Igalia) and [@gilbert][] create a proposal
23 | for an alternative pipe operator that does not use `this`.
24 | They start out [comparing F# pipes with Elixir pipes][first pipe preso].
25 | They also [explore other syntaxes such as “placeholder” pipes (i.e., Hack pipes)][I4].
26 |
27 | Meanwhile, [@rbuckton][] (Microsoft) discusses pipelining with the F# team
28 | and plans to simultaneously propose F# pipes and syntax
29 | for [partial function application][].
30 | Upon discovering [@littledan][] (Igalia)’s proposal for F#-or-Elixir pipes,
31 | [@rbuckton][] (Microsoft) finds that it already
32 | aligns with the F# pipe that he had been planning to present,
33 | and he switches to focusing on syntax for [partial function application][].
34 |
35 | ## 2017-07
36 | On [2017-09-26][S1], [@littledan][] makes a first
37 | [presentation for F#-or-Elixir pipes to TC39, successfully advancing to Stage 1][S1].
38 |
39 | However, parts of TC39 object to redundancy with a bind operator.
40 | A condition of the advancement to Stage 1 is that it would not be redundant
41 | with a bind operator; this will become relevant later.
42 |
43 | In addition, parts of TC39 object to aspects of F# pipes,
44 | such as how they handle `await` and `yield`.
45 |
46 | [@rbuckton][] (Microsoft) presents syntax for [partial function application][]
47 | [at the same meeting and succeeds in advancing to Stage 1][PFA S1].
48 | However, parts of TC39 push back against its syntax.
49 |
50 | ## 2017–2018
51 | [@littledan][] and collaborators [attempt to make a stopgap with `|> await`][I66]
52 | but encounter several conceptual and syntactic problems,
53 | including a problem related to unexpected automatic semicolon insertion.
54 | [@littledan][] decides to try to defer handling `await` at all to a later proposal;
55 | he also drops Elixir pipes in favor of F# pipes.
56 |
57 | On [2017-11-29][S2 2017], [@littledan][] makes
58 | [another presentation for F# pipes and does not succeed in advancing to Stage 2][S2 2017].
59 | During the presentation, he proposes to TC39 that `|> await` be deferred,
60 | but there is pushback from several other representatives, and presentation time overflows.
61 |
62 | Advancement of F# pipes is therefore stymied.
63 |
64 | [@gilbert][] suggests resurrecting Hack pipes,
65 | which were [previously explored in 2015][I4],
66 | as a solution to TC39’s blocking concerns.
67 | He also suggests two possible compromises that mix F# pipes and Hack pipes:
68 | [“split-mix pipes” and “smart-mix pipes”][I89].
69 | [@littledan][] (Igalia) agrees to investigate all three styles.
70 | [@mAAdhaTTah][] (pro-F#-pipes) and [@js-choi][] (slightly pro-Hack-pipes)
71 | volunteer to collaborate on competing specs:
72 | [@mAAdhaTTah][] writes a [spec for F# pipes][F# spec]
73 | and [@js-choi][] writes a [spec for smart-mix pipes][smart-mix-pipes spec].
74 |
75 | ## 2018–2020
76 | On [2018-03-22][S2 2018], [@littledan][] presents F# pipes again—alongside
77 | smart-mix pipes—in an
78 | [update presentation, trying to gain more consensus within TC39 for Stage 2][S2 2018].
79 | However, neither proposal is able to achieve much consensus among TC39 representatives
80 | due to syntactic concerns.
81 | Some TC39 representatives state that no pipe operator
82 | may be worth standardizing at all.
83 |
84 | Advancement of F# pipes therefore continues to be stymied;
85 | advancement of smart-mix pipes is also stymied.
86 |
87 | [@rbuckton][] (Microsoft) also presents [partial function application][]
88 | again [on 2018-07, attempting to advance it to Stage 2][PFA 2018-07].
89 | However, several TC39 representatives continue to push back against its syntax.
90 | [@syg][] (Google V8) also expresses “strong reservations” about PFA syntax
91 | increasing the “ease with which [developers] can allocate many many closures”,
92 | with regards to memory use.
93 | Partial function application is also unable to advance to Stage 2,
94 | and its advancement is therefore also stymied.
95 |
96 | [@codehag][] (Mozilla SpiderMonkey) meanwhile leads a
97 | [Mozilla user study about pipes][Mozilla study].
98 | Its results suggest that developers like and prefer smart-mix pipes slightly more,
99 | but they make slightly less errors with F# pipes.
100 | Her conclusions from the study were that no difference between smart-mix pipes or F# pipes
101 | was significant enough to justify any decision one way or the other.
102 | (There is also another Mozilla user study about partial function application,
103 | and its results suggests that the JavaScript community
104 | is much more interested in partial function application than any pipe operator.)
105 | The Mozilla SpiderMonkey team becomes weakly against any pipe operator.
106 |
107 | Work on F# pipes, smart-mix pipes, and PFA syntax stalls.
108 |
109 | For the three years, GitHub debate online and debate offline
110 | continues back and forth in circles:
111 | about the best way to increase the odds that smart-mix pipes vs. F# pipes
112 | will reach consensus in TC39.
113 |
114 | ## 2021-01
115 | The State of JS 2020 is published,
116 | and it reports that one of the [language’s top requested features][SoJS 20]
117 | is some kind of pipe operator.
118 | This galvanizes [@littledan][] (Igalia)’s further work on pipe,
119 | and he prepares a presentation for 2021-03’s TC39 meeting.
120 |
121 | ## 2021-03
122 | [@littledan][] (Igalia) is managing other projects
123 | and is now unable to devote time to the pipe operator.
124 | He presents about it again to TC39 and asks there for a new lead champion.
125 | [@tabatkins][] (Google), who also does much work in Web Platform standards
126 | such as HTML5 DOM and CSS,
127 | is personally enthusiastic about helping with any pipe operator
128 | and agrees to take on being co-champion.
129 | [@rbuckton][] (Microsoft) also agrees to co-champion the proposal.
130 |
131 | [@tabatkins][] (Google) publishes a [Gist comparing F#, Hack, and smart-mix pipes][Gist], concluding that they are all functionally the same,
132 | with only small differences in actual usage,
133 | and that all three would benefit everyone.
134 | Discussion is sparked in the Gist’s comments.
135 |
136 | Galvanized by the Gist,
137 | [@js-choi][] switches from writing the [smart-mix-pipes spec][]
138 | to writing a [Hack-pipes spec][].
139 |
140 | ## 2021-07
141 | [@tabatkins][] (Google) schedules a
142 | [TC39 incubator meeting devoted to pipes][2021-07 incubator]
143 | (see [general information on incubator calls][]).
144 | Attendees of special note in this meeting are [@syg][] (Google V8),
145 | [@littledan][] (Igalia), [@rbuckton][] (Microsoft), and [@ljharb][]. [@codehag][] (Mozilla SpiderMonkey) is unable to attend.
146 |
147 | [@tabatkins][] (Google) presents three choices to the attendees again:
148 | F# pipes, Hack pipes, and Elixir pipes.
149 |
150 | [@rbuckton][] (Microsoft) is still in favor of F# pipes with his proposed syntax for
151 | [partial function application][] (see [§ 2016–2017](#20162017)).
152 | [@rbuckton][] (Microsoft) debates with [@tabatkins][] (Google)
153 | about whether F# pipes or Hack pipes are more intuitive.
154 | (He also mentions that [@codehag][] (Mozilla SpiderMonkey)
155 | might be interested in co-championing partial function application without pipes,
156 | based on her user studies’ findings; see [§ 2018–2020](#20182020).)
157 |
158 | [@syg][] (Google V8) voices concerns again about engine performance
159 | of partial function application and F# pipes (see [§ 2018–2020](#20182020)).
160 |
161 | [@tabatkins][] (Google) and [@ljharb][] are supportive
162 | of either Hack pipes or F# pipes:
163 | “90%” F# pipes and “100%” Hack pipes.
164 | However, their 90% support of F# pipes would drop a lot if [@syg][]
165 | (Google V8)’s performance concerns about F# pipes are borne out.
166 |
167 | Nobody in the meeting seems to think Elixir pipes are a good fit for JavaScript.
168 |
169 | Most everyone in the meeting seems to be in favor of picking some style for pipes
170 | after three years of indecision.
171 |
172 | ## 2021-08
173 | [@tabatkins][] (Google) plans to present *some* pipe-operator style for Stage 2.
174 | Based on the results of the the preceding meeting, they pick Hack pipes.
175 | This has support from [@ljharb][] and some other TC39 representatives.
176 | [@js-choi][] (Indiana University) joins as co-champion.
177 |
178 | Through this month, [@tabatkins][] (Google) continues to debate offline
179 | with [@mAAdhaTTah][] regarding Hack pipes vs. F# pipes.
180 | As a result, [@mAAdhaTTah][] changes his mind from being in favor of F# pipes
181 | to being in favor of Hack pipes,
182 | deciding that [Hack pipes would be better for bridging functional programming][JDG essay]
183 | with the rest of the JavaScript ecosystem.
184 |
185 | [@rbuckton][] (Microsoft) joins in debating with [@tabatkins][] (Google) in late August.
186 | [@rbuckton][] (Microsoft) notes the groundswell of support within TC39 about Hack pipes
187 | due to “some of the limitations of F# pipes”.
188 | Therefore feeling that F# pipes would continue to be indefinitely stuck at an impasse,
189 | [@rbuckton][] (Microsoft) thus decides to give “tentative agreement” to Hack pipes.
190 | (See [@rbuckton’s narrative][].)
191 |
192 | ***
193 |
194 | On [2021-08-31][S2 2021], a formal Committee plenary occurs,
195 | and [@tabatkins][] (Google) therefore
196 | [presents Hack pipes as the tentative consensus among the champions,
197 | proposing that TC39 advance them to Stage 2][S2 2021].
198 | There are several responses from other representatives:
199 |
200 | [@ljharb][] voices concern that advancing pipe
201 | would kill any future bind operator (see [§ 2017-09](#2017-09)).
202 | Other representatives respond that Hack pipes are now orthogonal to any bind operator
203 | and would not kill it. [@ljharb][] decides not to block Stage 2.
204 |
205 | [@codehag][] (Mozilla SpiderMonkey) voices some concerns:
206 | the Mozilla SpiderMonkey team is still somewhat against any pipe operator,
207 | whatever the style.
208 | However, she decides that these concerns are not strong enough for her to block Stage 2.
209 |
210 | [@syg][] (Google V8), having previously expressed concerns about memory allocation
211 | encouraged by F# pipes and partial function application
212 | (see [§ 2021-07](#2021-07)), does not give any objection.
213 |
214 | [@rbuckton][] (Microsoft) continues to give tentative agreement to Hack pipes.
215 |
216 | No other representatives give objections to Stage 2.
217 | Hack pipes therefore succeed in advancing to Stage 2.
218 | [@tabatkins][] (Google) resolves to continue discussing concerns
219 | with [@rbuckton][] (Microsoft), [@codehag][] (Mozilla SpiderMonkey), [@mAAdhaTTah][],
220 | and others offline.
221 | They also discuss concerns with the community on GitHub,
222 | both in the [2021-03 comparison Gist][Gist]’s comments (see [§ 2021-03](#2021-03))
223 | and in the [pipe proposal’s issues][issues].
224 |
225 | ## 2021-09
226 | In order to explain this proposal’s history and process to other community members,
227 | [@js-choi][] (Indiana University) creates this document.
228 |
229 | Inspired by a [defense of unary functions][I233], [@js-choi][] (Indiana University)
230 | also creates a new proposal, [proposal-function-helpers][], that would add several Function
231 | helper methods. These include Function.pipe, pipeAsync, flow, and flowAsync.
232 |
233 | ## 2021-10
234 | Starting on 2021-10-25, another formal Commitee plenary occurs.
235 | The pipe operator is not presented at this meeting, although
236 | an [incubator meeting on 2021-11 is chartered][incubator charter 2021-11]
237 | for bikeshedding the pipe operator’s topic token.
238 |
239 | [On 2021-10-25, PFA syntax is presented again to the Committee
240 | plenary][2021-10 PFA] by [@rbuckton][] (Microsoft) for Stage 2.
241 | The Committee rejects this proposal; several representatives,
242 | including those from Mozilla SpiderMonkey and Google V8, state
243 | that there were insufficiently specific and compelling use cases presented,
244 | with high syntax cost and novelty in comparison to arrow functions.
245 |
246 | [On 2021-10-28, proposal-function-helpers is also presented to the
247 | Committee plenary][2021-10 Function] for Stage 1 by [@js-choi][].
248 | The Committee also rejects this proposal due to its being overly broad,
249 | and it requests that it be split up into multiple proposals.
250 | These split proposals would include a proposal specifically
251 | about [Function.pipe and flow][].
252 |
253 | ## 2021-12: Holistic-dataflow articles
254 | Since December, TC39 has continued to discuss the pipe operator in the greater
255 | context of “dataflow”.
256 |
257 | The “dataflow” proposals include the following:
258 |
259 | * The [pipe operator][Hack pipes] `… |> …` (aka “Hack pipes”)
260 | * [Function.pipe][] (a function version of the F# pipe operator)
261 | * The [bind-this][] operator `…::…` (and its variant [call-this][] `…@(…)`)
262 | * The [Extensions][] syntaxes `…::…`, `…::…:…`, and `const ::{ … } = …;`
263 | * [Partial function application][PFA syntax] `…~(…)` (aka “PFA syntax”)
264 |
265 | These dataflow proposals overlap in various complicated ways. Multiple TC39
266 | representatives expressed concerns about redundancies between these
267 | proposals—that the space as a whole needs to be holistically considered, that
268 | goals need to be more specifically articulated, and that there is not enough
269 | “syntax budget” in the language to approve all of these proposals. This applies
270 | to the pipe operator, as well as all the others in that list.
271 |
272 | ![][diagram]
273 |
274 | * In late December, [@js-choi][] wrote [an article detailing how these
275 | proposals overlap][2022-12 jschoi dataflow article].
276 | * [@tabatkins then wrote a response article](https://www.xanthir.com/b5Gd0) on
277 | their own blog.
278 | * Later, [@hax would also write another response
279 | article](https://hackmd.io/yDDJCsS-Sv2AJwo8arAn3w?view). (@hax is a TC39
280 | champion of the [Extensions][] syntaxes.)
281 |
282 | ## 2022-01: Plenary meeting
283 | On [January 26, 2022, a plenary meeting was held][2022-01 plenary] to discuss these overlapping proposals holistically. This discussion overflowed into an [ad-hoc meeting on the next day][2022-01 overflow].
284 |
285 | In these two meetings, TC39 representatives debated over such topics as:
286 | * Creating a unified language versus accommodating multiple programming paradigms (e.g., object oriented versus functional).
287 | * [TMTOWTDI][] versus [TOOWTDI][].
288 | * Whether generalized language features (like the Hack-style pipe operator) or specialized features (like Function.pipe, the F#-style pipe operator, and the bind-this operator) were more desirable.
289 | * Whether it is better for language features to be universal or prescriptive in their usage.
290 | * The merits of specific dataflow proposals, including the pipe operator.
291 |
292 | Support among TC39 representatives for the pipe operator as it is now (with a Hack-style topic reference) appears to range from strongly in favor to weakly against. Several representatives reaffirmed that they are moderately or strongly against F#-style syntax. Support for Function.pipe appears to be tepid: neither strongly positive or negative. For more details, see the [conclusions of the ad-hoc overflow meeting][2022-01 overflow conclusions].
293 |
294 | ## 2022-03 and 2022-04
295 | At the [2022-03 plenary, several candidate tokens for the topic reference were presented][2022-03 plenary pipe]. The Committee mildly preferred `@` as the topic reference. (However, a [delegate subsequently raised serious concerns about `@`][2022-04 serious @ concerns], and thus `@` was excluded again as a topic reference.)
296 |
297 | Additionally, an [update about call-this was also presented at the plenary][2022-03 plenary call-this]. The call-this proposal continues to polarize the Committee due to ecosystem-schism concerns.
298 |
299 | ## 2022-07
300 | [In the plenary on July 21, proposal-function-pipe-flow was formally presented to the Committee, and it was rejected for Stage 1][2022-07 plenary]. The Committee generally found its use cases not compelling enough compared to the pipe operator. Its champion subsequently withdrew it from consideration. (Eventually, after the pipe operator gains users, pain points with the pipe operator may be enough motivation to revive proposal-function-pipe-flow, but that would not occur for a long time.)
301 |
302 | There is another [incubator call chartered for more pipe-operator bikeshedding](https://github.com/tc39/incubator-agendas/issues/26), which might or might not occur before the [September plenary](https://github.com/tc39/agendas/blob/main/2022/09.md).
303 |
304 | [issues]: https://github.com/tc39/proposal-pipeline-operator/issues?q=is%3Aissue+
305 | [CONTRIBUTING.md]: https://github.com/tc39/proposal-pipeline-operator/blob/main/CONTRIBUTING.md
306 | [general information on incubator calls]: https://github.com/tc39/how-we-work/blob/master/incubator-calls.md
307 |
308 | [bind]: https://github.com/tc39/proposal-bind-operator
309 | [partial function application]: https://github.com/tc39/proposal-partial-application
310 | [proposal-function-helpers]: https://github.com/js-choi/proposal-function-helpers
311 | [Function.pipe and flow]: https://github.com/js-choi/proposal-function-pipe-flow
312 |
313 | [F# spec]: https://github.com/valtech-nyc/proposal-fsharp-pipelines/
314 | [smart-mix-pipes spec]: https://github.com/js-choi/proposal-smart-pipelines
315 | [Hack-pipes spec]: https://github.com/tc39/proposal-hack-pipes
316 | [Function.pipe]: https://github.com/js-choi/proposal-function-pipe-flow
317 | [bind-this]: https://github.com/tc39/proposal-bind-this
318 | [call-this]: https://github.com/tabatkins/proposal-call-this-operator
319 | [Extensions]: https://github.com/tc39/proposal-extensions
320 | [PFA syntax]: https://github.com/tc39/proposal-partial-application
321 | [diagram]: https://jschoi.org/21/es-dataflow/map/
322 | [Hack pipes]: https://github.com/tc39/proposal-pipeline-operator
323 | [TMTOWTDI]: https://en.wikipedia.org/wiki/There%27s_more_than_one_way_to_do_it
324 | [TOOWTDI]: https://wiki.python.org/moin/TOOWTDI
325 |
326 | [first pipe preso]: https://docs.google.com/presentation/d/1qiWFzi5dkjuUVGcFXwypuQbEbZk-BV7unX0bYurcQsA/edit#slide=id.g1fa08b5c5c_0_93
327 |
328 | [I4]: https://github.com/tc39/proposal-pipeline-operator/issues/4
329 | [I75]: https://github.com/tc39/proposal-pipeline-operator/issues/75
330 | [I66]: https://github.com/tc39/proposal-pipeline-operator/pull/66
331 | [I89]: https://github.com/tc39/proposal-pipeline-operator/issues/89
332 | [I233]: https://github.com/tc39/proposal-pipeline-operator/issues/233
333 |
334 | [S1]: https://github.com/tc39/notes/blob/master/meetings/2017-09/sept-26.md#11iia-pipeline-operator
335 | [S2 2017]: https://github.com/tc39/notes/blob/master/meetings/2017-11/nov-29.md#9iii-pipeline-operator-for-stage-2
336 | [PFA S1]: https://github.com/tc39/notes/blob/master/meetings/2017-09/sept-28.md#13i-partial-application
337 | [S2 2018]: https://github.com/tc39/notes/blob/master/meetings/2018-03/mar-22.md#10ive-pipeline-operator
338 | [PFA 2018-07]: https://github.com/tc39/notes/blob/master/meetings/2018-07/july-25.md#partial-application
339 | [2021-07 incubator]: https://github.com/tc39/incubator-agendas/blob/master/notes/2021/06-17.md#pipeline
340 | [Mozilla study]: https://github.com/tc39/notes/blob/master/meetings/2019-06/june-6.md#javascript-and-syntax-research-methods
341 | [S2 2021]: https://github.com/tc39/notes/blob/master/meetings/2021-08/aug-31.md#pipeline-operator-for-stage-2
342 | [incubator charter 2021-11]: https://github.com/tc39/incubator-agendas/issues/21
343 | [2021-10 PFA]: https://github.com/tc39/notes/blob/master/meetings/2021-10/oct-25.md#partial-function-application-for-stage-2
344 | [2021-10 Function]: https://github.com/tc39/notes/blob/master/meetings/2021-10/oct-28.md#function-helpers
345 | [2022-01 plenary]: https://github.com/tc39/notes/blob/main/meetings/2022-01/jan-26.md#holistic-discussion-of-tc39-dataflow-proposals
346 | [2022-01 overflow]: https://github.com/tc39/incubator-agendas/blob/main/notes/2022/01-27.md
347 | [2022-01 overflow conclusions]: https://github.com/tc39/incubator-agendas/blob/main/notes/2022/01-27.md#conclusions
348 | [2022-12 jschoi dataflow article]: https://jschoi.org/21/es-dataflow/
349 | [2022-03 plenary pipe]: https://github.com/tc39/notes/blob/main/meetings/2022-03/mar-29.md#bikeshedding-the-pipe-operator-topic-token
350 | [2022-03 plenary call-this]: https://github.com/tc39/notes/blob/main/meetings/2022-03/mar-29.md#bikeshedding-call-this-syntax
351 | [2022-04 serious @ concerns]: https://github.com/tc39/proposal-pipeline-operator/issues/91#issuecomment-1084946624
352 | [2022-07 plenary]: https://github.com/tc39/notes/blob/main/meetings/2022-07/jul-21.md#functionpipe--flow-for-stage-1
353 |
354 | [TC39 process]: https://tc39.es/process-document/
355 | [Gist]: https://gist.github.com/tabatkins/1261b108b9e6cdab5ad5df4b8021bcb5
356 | [@rbuckton’s narrative]: https://github.com/tc39/proposal-pipeline-operator/issues/91#issuecomment-917645179
357 | [SoJS 20]: https://2020.stateofjs.com/en-US/opinions/?missing_from_js
358 | [JDG essay]: https://jamesdigioia.com/hack-pipe-for-functional-programmers-how-i-learned-to-stop-worrying-and-love-the-placeholder/
359 |
360 | [@littledan]: https://github.com/littledan/
361 | [@gilbert]: https://github.com/gilbert/
362 | [@tabatkins]: https://github.com/tabatkins/
363 | [@codehag]: https://github.com/codehag/
364 | [@mAAdhaTTah]: https://github.com/mAAdhaTTah/
365 | [@js-choi]: https://github.com/js-choi/
366 | [@syg]: https://github.com/syg/
367 | [@ljharb]: https://github.com/ljharb/
368 | [@rbuckton]: https://github.com/rbuckton/
369 | [@zenparsing]: https://github.com/zenparsing
370 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright 2021 J. S. Choi, James DiGioia, Ron Buckton, Tab Atkins-Bittner
2 |
3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
4 |
5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6 |
7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8 |
9 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10 |
11 | **This software is provided by the copyright holders and contributors “as is” and any express or implied warranties, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose are disclaimed. In no event shall the copyright holder or contributors be liable for any direct, indirect, incidental, special, exemplary, or consequential damages (including, but not limited to, procurement of substitute goods or services; loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this software, even if advised of the possibility of such damage.**
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Pipe Operator (`|>`) for JavaScript
2 |
3 | * **Stage**: 2
4 | * **Champions**: J. S. Choi, James DiGioia, Ron Buckton, Tab Atkins-Bittner, \[list incomplete]
5 | * **Former champions**: Daniel Ehrenberg
6 | * **[Specification][]**
7 | * **[Contributing guidelines][]**
8 | * **[Proposal history][]**
9 | * **Babel plugin**: [Implemented in v7.15][Babel 7.15]. See [Babel documentation][].
10 |
11 | (This document uses `%`
12 | as the placeholder token for the topic reference.
13 | This will ***almost certainly not be the final choice***;
14 | see [the token bikeshedding discussion][token bikeshedding] for details.)
15 |
16 | [specification]: http://tc39.github.io/proposal-pipeline-operator/
17 | [Babel 7.15]: https://babeljs.io/blog/2021/07/26/7.15.0#hack-style-pipeline-operator-support-13191httpsgithubcombabelbabelpull13191-13416httpsgithubcombabelbabelpull13416
18 | [Babel documentation]: https://babeljs.io/docs/en/babel-plugin-proposal-pipeline-operator
19 | [token bikeshedding]: https://github.com/tc39/proposal-pipeline-operator/issues/91
20 | [contributing guidelines]: https://github.com/tc39/proposal-pipeline-operator/blob/main/CONTRIBUTING.md
21 | [proposal history]: https://github.com/tc39/proposal-pipeline-operator/blob/main/HISTORY.md
22 |
23 | ## Why a pipe operator
24 | In the State of JS 2020 survey, the **fourth top answer** to
25 | [“What do you feel is currently missing from
26 | JavaScript?”](https://2020.stateofjs.com/en-US/opinions/#missing_from_js)
27 | was a **pipe operator**. Why?
28 |
29 | When we perform **consecutive operations** (e.g., function calls)
30 | on a **value** in JavaScript,
31 | there are currently two fundamental styles:
32 | * passing the value as an argument to the operation
33 | (**nesting** the operations if there are multiple operations),
34 | * or calling the function as a method on the value
35 | (**chaining** more method calls if there are multiple methods).
36 |
37 | That is, `three(two(one(value)))` versus `value.one().two().three()`.
38 | However, these styles differ much in readability, fluency, and applicability.
39 |
40 | ### Deep nesting is hard to read
41 | The first style, **nesting**, is generally applicable –
42 | it works for any sequence of operations:
43 | function calls, arithmetic, array/object literals, `await` and `yield`, etc.
44 |
45 | However, nesting is **difficult to read** when it becomes deep:
46 | the flow of execution moves **right to left**,
47 | rather than the left-to-right reading of normal code.
48 | If there are **multiple arguments** at some levels,
49 | reading even bounces **back and forth**:
50 | our eyes must **jump left** to find a function name,
51 | and then they must **jump right** to find additional arguments.
52 | Additionally, **editing** the code afterwards can be fraught:
53 | we must find the correct **place to insert** new arguments
54 | among **many nested parentheses**.
55 |
56 |
57 | Real-world example
58 |
59 | Consider this [real-world code from React](https://github.com/facebook/react/blob/17.0.2/scripts/jest/jest-cli.js#L295).
60 |
61 | ```js
62 | console.log(
63 | chalk.dim(
64 | `$ ${Object.keys(envars)
65 | .map(envar =>
66 | `${envar}=${envars[envar]}`)
67 | .join(' ')
68 | }`,
69 | 'node',
70 | args.join(' ')));
71 | ```
72 |
73 | This real-world code is made of **deeply nested expressions**.
74 | In order to read its flow of data, a human’s eyes must first:
75 |
76 | 1. Find the **initial data** (the innermost expression, `envars`).
77 | 2. And then scan **back and forth** repeatedly from **inside out**
78 | for each data transformation,
79 | each one either an easily missed prefix operator on the left
80 | or a suffix operators on the right:
81 |
82 | 1. `Object.keys()` (left side),
83 | 2. `.map()` (right side),
84 | 3. `.join()` (right side),
85 | 4. A template literal (both sides),
86 | 5. `chalk.dim()` (left side), then
87 | 6. `console.log()` (left side).
88 |
89 | As a result of deeply nesting many expressions
90 | (some of which use **prefix** operators,
91 | some of which use **postfix** operators,
92 | and some of which use **circumfix** operators),
93 | we must check **both left and right sides**
94 | to find the **head** of **each expression**.
95 |
96 |
97 |
98 | ### Method chaining is limited
99 | The second style, **method chaining**, is **only** usable
100 | if the value has the functions designated as **methods** for its class.
101 | This **limits** its applicability.
102 | But **when** it applies, thanks to its postfix structure,
103 | it is generally more usable and **easier** to read and write.
104 | Code execution flows **left to right**.
105 | Deeply nested expressions are **untangled**.
106 | All arguments for a function call are **grouped** with the function’s name.
107 | And editing the code later to **insert or delete** more method calls is trivial,
108 | since we would just have to put our cursor in one spot,
109 | then start typing or deleting one **contiguous** run of characters.
110 |
111 | Indeed, the benefits of method chaining are **so attractive**
112 | that some **popular libraries contort** their code structure
113 | specifically to allow **more method chaining**.
114 | The most prominent example is **[jQuery][]**, which
115 | still remains the **most popular JS library** in the world.
116 | jQuery’s core design is a single über-object with dozens of methods on it,
117 | all of which return the same object type so that we can **continue chaining**.
118 | There is even a name for this style of programming:
119 | **[fluent interfaces][]**.
120 |
121 | [jQuery]: https://jquery.com/
122 | [fluent interfaces]: https://en.wikipedia.org/wiki/Fluent_interface
123 |
124 | Unfortunately, for all of its fluency,
125 | **method chaining** alone cannot accommodate JavaScript’s **other syntaxes**:
126 | function calls, arithmetic, array/object literals, `await` and `yield`, etc.
127 | In this way, method chaining remains **limited** in its **applicability**.
128 |
129 | ### Pipe operators combine both worlds
130 | The pipe operator attempts to marry the **convenience** and ease of **method chaining**
131 | with the wide **applicability** of **expression nesting**.
132 |
133 | The general structure of all the pipe operators is
134 | `value |>` e1 `|>` e2 `|>` e3,
135 | where e1, e2, e3
136 | are all expressions that take consecutive values as their parameters.
137 | The `|>` operator then does some degree of magic to “pipe” `value`
138 | from the lefthand side into the righthand side.
139 |
140 |
141 | Real-world example, continued
142 |
143 | Continuing this deeply nested [real-world code from React][react/scripts/jest/jest-cli.js]:
144 |
145 | ```js
146 | console.log(
147 | chalk.dim(
148 | `$ ${Object.keys(envars)
149 | .map(envar =>
150 | `${envar}=${envars[envar]}`)
151 | .join(' ')
152 | }`,
153 | 'node',
154 | args.join(' ')));
155 | ```
156 |
157 | …we can **untangle** it as such using a pipe operator
158 | and a placeholder token (`%`) standing in for the previous operation’s value:
159 |
160 | ```js
161 | Object.keys(envars)
162 | .map(envar => `${envar}=${envars[envar]}`)
163 | .join(' ')
164 | |> `$ ${%}`
165 | |> chalk.dim(%, 'node', args.join(' '))
166 | |> console.log(%);
167 | ```
168 |
169 | Now, the human reader can **rapidly find** the **initial data**
170 | (what had been the most innermost expression, `envars`),
171 | then **linearly** read, from **left to right**,
172 | each transformation on the data.
173 |
174 |
175 |
176 | ### Temporary variables are often tedious
177 | One could argue that using **temporary variables**
178 | should be the only way to untangle deeply nested code.
179 | Explicitly naming every step’s variable
180 | causes something similar to method chaining to happen,
181 | with similar benefits to reading and writing code.
182 |
183 |
184 | Real-world example, continued
185 |
186 | For example, using our previous modified
187 | [real-world example from React][react/scripts/jest/jest-cli.js]:
188 |
189 | ```js
190 | Object.keys(envars)
191 | .map(envar => `${envar}=${envars[envar]}`)
192 | .join(' ')
193 | |> `$ ${%}`
194 | |> chalk.dim(%, 'node', args.join(' '))
195 | |> console.log(%);
196 | ```
197 |
198 | …a version using temporary variables would look like this:
199 |
200 | ```js
201 | const envarString = Object.keys(envars)
202 | .map(envar => `${envar}=${envars[envar]}`)
203 | .join(' ');
204 | const consoleText = `$ ${envarString}`;
205 | const coloredConsoleText = chalk.dim(consoleText, 'node', args.join(' '));
206 | console.log(coloredConsoleText);
207 | ```
208 |
209 |
210 |
211 | But there are reasons why we encounter deeply nested expressions
212 | in each other’s code **all the time in the real world**,
213 | **rather than** lines of temporary variables.
214 | And there are reasons why the **method-chain-based [fluent interfaces][]**
215 | of jQuery, Mocha, and so on are still **popular**.
216 |
217 | It is often simply too **tedious and wordy** to **write**
218 | code with a long sequence of temporary, single-use variables.
219 | It is arguably even tedious and visually noisy for a human to **read**, too.
220 |
221 | If [**naming** is one of the **most difficult tasks** in programming][naming hard],
222 | then programmers will **inevitably avoid naming** variables
223 | when they perceive their benefit to be relatively small.
224 |
225 | [naming hard]: https://martinfowler.com/bliki/TwoHardThings.html
226 |
227 | ### Reusing temporary variables is prone to unexpected mutation
228 | One could argue that using a single **mutable variable** with a short name
229 | would reduce the wordiness of temporary variables, achieving
230 | similar results as with the pipe operator.
231 |
232 |
233 | Real-world example, continued
234 |
235 | For example, our previous modified
236 | [real-world example from React][react/scripts/jest/jest-cli.js]
237 | could be re-written like this:
238 | ```js
239 | let _;
240 | _ = Object.keys(envars)
241 | .map(envar => `${envar}=${envars[envar]}`)
242 | .join(' ');
243 | _ = `$ ${_}`;
244 | _ = chalk.dim(_, 'node', args.join(' '));
245 | _ = console.log(_);
246 | ```
247 |
248 |
249 |
250 | But code like this is **not common** in real-world code.
251 | One reason for this is that mutable variables can **change unexpectedly**,
252 | causing silent bugs that are hard to find.
253 | For example, the variable might be accidentally referenced in a closure.
254 | Or it might be mistakenly reassigned within an expression.
255 |
256 |
257 | Example code
258 |
259 | ```js
260 | // setup
261 | function one () { return 1; }
262 | function double (x) { return x * 2; }
263 |
264 | let _;
265 | _ = one(); // _ is now 1.
266 | _ = double(_); // _ is now 2.
267 | _ = Promise.resolve().then(() =>
268 | // This does *not* print 2!
269 | // It prints 1, because `_` is reassigned downstream.
270 | console.log(_));
271 |
272 | // _ becomes 1 before the promise callback.
273 | _ = one(_);
274 | ```
275 |
276 | This issue would not happen with the pipe operator.
277 | The topic token cannot be reassigned, and
278 | code outside of each step cannot change its binding.
279 |
280 | ```js
281 | let _;
282 | _ = one()
283 | |> double(%)
284 | |> Promise.resolve().then(() =>
285 | // This prints 2, as intended.
286 | console.log(%));
287 |
288 | _ = one();
289 | ```
290 |
291 |
292 |
293 | For this reason, code with mutable variables is also harder to read.
294 | To determine what the variable represents at any given point,
295 | you must to **search the entire preceding scope** for places where it is **reassigned**.
296 |
297 | The topic reference of a pipeline, on the other hand, has a limited lexical scope,
298 | and its binding is immutable within its scope.
299 | It cannot be accidentally reassigned, and it can be safely used in closures.
300 |
301 | Although the topic value also changes with each pipeline step,
302 | we only scan the previous step of the pipeline to make sense of it,
303 | leading to code that is easier to read.
304 |
305 | ### Temporary variables must be declared in statements
306 | Another benefit of the pipe operator over sequences of assignment statements
307 | (whether with mutable or with immutable temporary variables)
308 | is that they are **expressions**.
309 |
310 | Pipe expressions are expressions that can be directly returned,
311 | assigned to a variable, or used in contexts such as JSX expressions.
312 |
313 | Using temporary variables, on the other hand, requires sequences of statements.
314 |
315 |
316 | Examples
317 |
318 |
2 | title: ES pipe operator (2021)
3 | status: proposal
4 | stage: 2
5 | location: https://github.com/tc39/proposal-pipeline-operator
6 | copyright: false
7 | contributors: J. S. Choi, James DiGioia, Ron Buckton, Tab Atkins-Bittner
8 |
9 |
10 |
11 |
12 |
13 |
Introduction
14 |
This is the formal specification for a proposed Hack-style pipe
15 | operator `|>` in JavaScript. It modifies the original ECMAScript specification with
17 | several new or revised clauses. See the proposal's
19 | explainer for the proposal's background, motivation, and usage examples.
20 |
21 |
This document presumptively uses `%` as the token
22 | for the topic reference. This choice of token is not a final decision;
23 | `%` could instead be `^` or some other token.
It presumptively uses `%` as the placeholder token for the
42 | topic reference. This choice of token is not a final decision; `%`
43 | could instead be `^` or some other token.
It presumptively uses `%` as the placeholder token for the
75 | topic reference. This choice of token is not a final decision; `%`
76 | could instead be `^` or some other token.
Several early error rules for |ScriptBody| and for
118 | |ModuleItemList|, as well as a step in CreateDynamicFunction,
119 | use the ContainsOuterTopic operation to check for any unbound topic reference `%`.
120 | Any inner topic reference within a |PipeBody| is hidden from these rules,
121 | preventing them from triggering the rules during program
122 | compilation.
123 |
124 |
This guarantees that any topic reference in a program must be
125 | present within a topic-binding environment
126 | created by a |PipeBody| within that program.
127 |
128 |
129 |
Every grammar production alternative in this specification which is not listed below implicitly has the following default definition of ContainsOuterTopic:
130 |
131 |
132 | 1. For each child node _child_ of this Parse Node, do
133 | 1. If _child_ is an instance of `%`, return *true*.
134 | 1. If _child_ is an instance of a nonterminal, and if ContainsOuterTopic of _child_ is *true*, return *true*.
135 | 1. Return *false*.
136 |
137 |
138 | MultiplicativeExpression : MultiplicativeExpression MultiplicativeOperator ExponentiationExpression
139 |
140 |
141 | 1. If ContainsOuterTopic of |MultiplicativeExpression| is *true*, or if ContainsOuterTopic of |ExponentiationExpression| is *true*, return *true*.
142 | 1. Return *false*.
143 |
144 |
145 | PipeBody : AssignmentExpression
146 |
147 |
148 | 1. Return *false*.
149 |
150 |
151 |
152 |
153 |
154 |
It presumptively uses `%` as the placeholder token for the
166 | topic reference. This choice of token is not a final decision; `%`
167 | could instead be `^` or some other token.
Determine whether an Environment Record is a
224 | topic-binding environment. Return *true* if it establishes a
225 | topic binding and *false* if it does not.
It presumptively uses `%` as the placeholder token for the
240 | topic reference. This choice of token is not a final decision; `%`
241 | could instead be `^` or some other token.
242 |
243 |
244 |
245 |
Declarative Environment Records have the additional state fields
246 | listed in .
247 |
248 |
251 |
252 |
253 |
254 |
Method
255 |
Value
256 |
Purpose
257 |
258 |
259 |
260 |
261 |
[[TopicValues]]
262 |
List of any
263 |
If the declarative Environment Record is a topic-binding
264 | environment, then [[TopicValues]] is a List containing the one element
265 | which is the environment's topic value (that is, the value of
266 | the topic reference within its program scope).
267 | Otherwise, the value of [[TopicValues]] is an empty List.
268 |
269 |
270 |
271 |
272 |
273 |
274 |
[[TopicValues]] is a List in order to be forward compatible with
275 | future extensions that would use more than one topic value, e.g., "pipe functions".
278 |
279 |
280 |
Declarative Environment Records support all of the abstract methods
281 | of Environment Records listed in .
282 | In addition, declarative Environment Records support the methods listed
283 | in .
284 |
285 |
286 |
287 |
288 |
289 |
Method
290 |
Purpose
291 |
292 |
293 |
294 |
295 |
BindTopicValues(V)
296 |
Establish the immutable topic binding of this
297 | Environment Record and set the topic binding's value.
298 | _V_ is a List containing the one element which is the
299 | topic value and is a value of any ECMAScript language
300 | type. Afterward, the Environment Record is a
301 | topic-binding environment, and the value returned by the
302 | Environment Record's HasTopicBinding method is *true*.
303 | This method cannot be called more than once on any
304 | single Environment Record.
305 |
306 |
307 |
308 |
309 |
310 |
311 |
BindTopicValues accepts a List argument rather than a single-value
312 | argument in order to be forward compatible with future extensions that
313 | would use more than one topic value, e.g., "pipe functions".
316 |
317 |
318 |
319 |
The behaviour of the concrete and additional specification
320 | methods for declarative Environment Records is defined by the following
321 | algorithms.
The concrete Environment Record method HasTopicBinding for
332 | declarative Environment Records returns whether the Environment Record
333 | is a topic-binding environment. The value is *true* only if its
334 | BindTopicValues method has been called.
335 |
336 | 1. Let _envRec_ be the declarative Environment Record for which the
337 | method was invoked.
338 | 1. Assert: _envRec_.[[TopicValues]] is a List.
339 | 1. If _envRec_.[[TopicValues]] is an empty List, then return *false*.
340 | 1. Return *true*.
341 |
342 |
343 |
344 |
345 |
The method BindTopicValues for declarative Environment Records may
352 | only be called on an Environment Record when it is not yet a
353 | topic-binding environment, after which it does become a topic-binding
354 | environment.
355 |
356 | 1. Assert: _V_ is a List.
357 | 1. Let _envRec_ be the declarative Environment Record for which the
358 | method was invoked.
359 | 1. Assert: _envRec_.HasTopicBinding() is *false*.
360 | 1. Set _envRec_.[[TopicValues]] to _V_.
361 | 1. Assert: _envRec_.HasTopicBinding() is *true*.
362 | 1. Return NormalCompletion(~empty~).
363 |
364 |
365 |
366 |
367 |
368 |
It presumptively uses `%` as the placeholder token for the
415 | topic reference. This choice of token is not a final decision; `%`
416 | could instead be `^` or some other token.
417 |
418 |
419 |
The topic binding of a declarative Environment Record
420 | immutably binds the topic reference `%` to one value of any
421 | ECMAScript language type (called the topic value or simply
422 | the topic), within that declarative Environment Record, at
423 | the time of the Environment Record's instantiation. The topic of a
424 | declarative Environment Record conceptually serves as the value that its
425 | lexical context is "about".
426 |
427 |
A topic-binding environment is a declarative Environment
428 | Record that establishes a topic binding.
429 | The topic environment of the running execution context is its
430 | LexicalEnvironment's innermost Environment Record that is also a
431 | topic-binding environment (or *null* if no such Environment Record
432 | exists), as defined by the abstract operator GetTopicEnvironment.
433 |
434 |
435 |
An Environment Record is a topic-binding environment
436 | only when it is a declarative Environment Record that
437 | was created by a |PipeBody|.
453 | EvaluateWithTopics (
454 | _topicValues_: a List,
455 | _expression_: a Parse Node,
456 | )
457 |
458 |
459 |
460 | 1. Assert: _topicValues_ is a List.
461 | 1. Let _outerEnv_ be the running execution context's LexicalEnvironment.
462 | 1. Let _topicEnv_ be NewDeclarativeEnvironment(_outerEnv_).
463 | 1. Perform ! _topicEnv_.BindTopicValues(_topicValues_).
464 | 1. Set the running execution context's LexicalEnvironment to
465 | _topicEnv_.
466 | 1. Let _result_ be the result of evaluating _expression_.
467 | 1. Set the running execution context's LexicalEnvironment to _outerEnv_.
468 | 1. Return _result_.
469 |
470 |
471 |
EvaluateWithTopics creates a new topic-binding environment and
472 | evaluates the given _expression_ with that as its topic environment.
473 | The previous Lexical Environment is restored afterward.
474 |
475 |
476 |
477 |
478 |
GetTopicEnvironment ( )
479 |
480 |
481 | 1. Let _envRec_ be the running execution context's LexicalEnvironment.
482 | 1. Repeat,
483 | 1. Let _status_ be _envRec_.HasTopicBinding().
484 | 1. If _status_ is *true*, return _envRec_.
485 | 1. If _envRec_ is a global Environment Record, return *null*.
486 | 1. Let _outer_ be the value of _envRec_.[[OuterEnv]].
487 | 1. Set _envRec_ to _outer_.
488 | 1. Return _envRec_.
489 |
490 |
491 |
GetTopicEnvironment returns the running execution context's topic environment
492 | (i.e., its LexicalEnvironment's innermost topic-binding environment)
493 | or *null* if the running execution context has no topic binding.
494 |
495 |
496 |
497 |
498 |
GetPrimaryTopicValue ( )
499 |
500 |
501 | 1. Let _topicEnv_ be GetTopicEnvironment().
502 | 1. Assert: _topicEnv_ is a declarative Environment Record.
503 | 1. Assert: _topicEnv_.HasTopicBinding() is *true*.
504 | 1. Let _topicValues_ be _envRec_.[[TopicValues]].
505 | 1. Assert: _topicValues_ has at least one element.
506 | 1. Return _topicValues_[0].
507 |
508 |
509 |
GetPrimaryTopicValue returns the topic value
510 | of the running execution context's topic environment.
511 | It may be called only when the running execution context's topic environment
512 | is not *null*.
It presumptively uses `%` as the placeholder token for the
530 | topic reference. This choice of token is not a final decision; `%`
531 | could instead be `^` or some other token.
It presumptively uses `%` as the placeholder token for the
573 | topic reference. This choice of token is not a final decision; `%`
574 | could instead be `^` or some other token.
It presumptively uses `%` as the placeholder token for the
607 | topic reference. This choice of token is not a final decision; `%`
608 | could instead be `^` or some other token.
609 |
610 |
611 |
612 |
The topic reference, which is the token `%`, is a
613 | nullary operator that evaluates to the current Environment Record's
614 | topic value. The topic reference acts as if it were a special variable,
615 | implicitly bound to the topic value, yet still lexically scoped. But
616 | `%` is not actually an |IdentifierName| and the topic reference is
617 | not a variable, and it cannot be bound by typical assignment.
618 | Instead, the topic reference is immutably bound to a value
619 | during the instantiation of any topic-binding environment by a |PipeBody|.
620 |
621 |
The concept of the topic binding is further discussed in Topic Bindings and in Declarative Environment
624 | Records.
625 |
626 |
627 |
628 |
An unbound topic reference is a topic reference that
629 | is not present within any topic-binding environment created by a |PipeBody|.
630 | All unbound topic references are invalid syntax.
631 | Several early error rules for |ScriptBody| and for
632 | |ModuleItemList|, as well as a step in CreateDynamicFunction,
633 | use ContainsOuterTopic to check for any unbound topic reference `%`.
634 | Any inner topic reference within a |PipeBody| is hidden from these rules,
635 | preventing them from triggering the rules during program
636 | compilation.
637 |
638 |
This guarantees that every topic reference in a program must be
639 | present within a topic-binding environment
640 | created by a |PipeBody| within that program.
641 |
642 |
643 |
644 |
Runtime Semantics: Evaluation
645 |
646 |
A topic reference may be evaluated only when
647 | the running execution context's topic environment is not *null*.
648 | This is syntactically enforced by early error rules
649 | for |ScriptBody| and for |ModuleItemList|,
650 | as well as a step in CreateDynamicFunction.
651 | These rules use ContainsOuterTopic to check for any unbound topic reference.
It presumptively uses `%` as the placeholder token for the
688 | topic reference. This choice of token is not a final decision; `%`
689 | could instead be `^` or some other token.
690 |
691 |
692 | PipeBody : AssignmentExpression
693 |
694 | 1. It is a Syntax Error if |PipeBody| ContainsOuterTopic is *false*.
695 |
696 |
697 |
698 |
A |PipeBody| must use its topic at least once.
699 | `value |> foo + 1` is an early error,
700 | because ContainsOuterTopic of its |PipeBody| is *false*.
701 | This design is such because omission of any topic reference from a |PipeBody|
702 | is almost certainly an accidental programmer error.
703 |
704 |
705 | PipeBody : AssignmentExpression
706 |
707 | 1. It is a Syntax Error if one of the following productions is covering |PipeBody|:
708 | * |ShortCircuitExpression| ? |AssignmentExpression| : |AssignmentExpression|
709 | * |YieldExpression|
710 | * |ArrowFunction|
711 | * |AsyncArrowFunction|
712 | * |LeftHandSideExpression| `=` |AssignmentExpression|
713 | * |LeftHandSideExpression| |AssignmentOperator| |AssignmentExpression|
714 | * |LeftHandSideExpression| `&&=` |AssignmentExpression|
715 | * |LeftHandSideExpression| `||=` |AssignmentExpression|
716 | * |LeftHandSideExpression| `??=` |AssignmentExpression|
717 |
718 |
719 |
720 |
A |PipeBody| must not be an unparenthesized |AssignmentExpression|, such as |YieldExpression|, |ArrowFunction|, or |ConditionalExpression|—unless it is a |ShortCircuitExpression|.
721 |
This is to prevent confusing expressions from being valid, such as:
722 |
723 | x |> yield % |> % + 1 // Syntax Error
724 |
725 |
This expression would otherwise be equivalent to:
726 |
727 | x |> (yield % |> % + 1)
728 |
729 |
Likewise, this expression:
730 |
731 | x |> y ? % : z |> % + 1 // Syntax Error
732 |
733 |
…would otherwise be equivalent to:
734 |
735 | x |> (y ? % : z |> % + 1)
736 |
737 |
These expressions are visually unclear and are therefore made invalid. The developer may make them valid with explicit parentheses:
738 |
739 | x |> (yield %) |> % + 1
740 | x |> (yield % |> % + 1)
741 | x |> (y ? % : z) |> % + 1
742 | x |> (y ? % : z |> % + 1)
743 |
744 |
745 |
746 |
747 |
748 |
Runtime Semantics: Evaluation
749 |
750 | PipeExpression : ConditionalExpression `|>` PipeBody
751 |
752 | 1. Let _inputRef_ be the result of evaluating _ConditionalExpression_.
753 | 1. Let _inputValues_ be « ? GetValue(_inputRef_) ».
754 | 1. Let _outputValue_ be ? EvaluateWithTopics(_inputValues_, |PipeBody|).
755 | 1. Return _outputValue_.
756 |
757 |
758 |
759 |
760 |
761 |
762 |
It presumptively uses `%` as the placeholder token for the
773 | topic reference. This choice of token is not a final decision; `%`
774 | could instead be `^` or some other token.
775 |
776 |
777 |
778 |
Static Semantics: Early Errors
779 |
780 | Script : ScriptBody
781 |
782 |
783 | It is a Syntax Error if ContainsOuterTopic of |ScriptBody| is *true*.
784 |
785 |
786 |
787 |
788 |
An early error rule uses ContainsOuterTopic
789 | to check for any unbound topic reference.
790 | Any inner topic reference within a |PipeBody| is hidden from this rule,
791 | preventing them from triggering the rule during program
792 | compilation.
793 |
794 |
This guarantees that every topic reference in a |Script| must be
795 | present within a topic-binding environment created
796 | by a |PipeBody| within that |Script|.
It presumptively uses `%` as the placeholder token for the
814 | topic reference. This choice of token is not a final decision; `%`
815 | could instead be `^` or some other token.
816 |
817 |
818 |
819 |
Static Semantics: Early Errors
820 | ModuleBody : ModuleItemList
821 |
822 |
823 | It is a Syntax Error if ContainsOuterTopic of |ModuleItemList|
824 | is *true*.
825 |
826 |
827 |
828 |
829 |
830 |
An early error rule uses ContainsOuterTopic
831 | to check for any unbound topic reference.
832 | Any inner topic reference within a |PipeBody| is hidden from this rule,
833 | preventing them from triggering the rule during program
834 | compilation.
835 |
836 |
This guarantees that every topic reference in a |ModuleBody| must be
837 | present within a topic-binding environment created
838 | by a |PipeBody| within that |ModuleBody|.
It presumptively uses `%` as the placeholder token for the
866 | topic reference. This choice of token is not a final decision; `%`
867 | could instead be `^` or some other token.
868 |
869 |
870 |
871 | 1. Assert: The execution context stack has at least two elements.
872 | 1. Let _callerContext_ be the second to top element of the execution context stack.
873 | 1. Let _callerRealm_ be _callerContext_'s Realm.
874 | 1. Let _calleeRealm_ be the current Realm Record.
875 | 1. Perform ? HostEnsureCanCompileStrings(_callerRealm_, _calleeRealm_).
876 | 1. If _newTarget_ is *undefined*, set _newTarget_ to _constructor_.
877 | 1. If _kind_ is ~normal~, then
878 | 1. Let _exprSym_ be the grammar symbol |FunctionExpression|.
879 | 1. Let _bodySym_ be the grammar symbol |FunctionBody[~Yield, ~Await]|.
880 | 1. Let _parameterSym_ be the grammar symbol |FormalParameters[~Yield, ~Await]|.
881 | 1. Let _fallbackProto_ be *"%Function.prototype%"*.
882 | 1. Else if _kind_ is ~generator~, then
883 | 1. Let _exprSym_ be the grammar symbol |GeneratorExpression|.
884 | 1. Let _bodySym_ be the grammar symbol |GeneratorBody|.
885 | 1. Let _parameterSym_ be the grammar symbol |FormalParameters[+Yield, ~Await]|.
886 | 1. Let _fallbackProto_ be *"%GeneratorFunction.prototype%"*.
887 | 1. Else if _kind_ is ~async~, then
888 | 1. Let _exprSym_ be the grammar symbol |AsyncFunctionExpression|.
889 | 1. Let _bodySym_ be the grammar symbol |AsyncFunctionBody|.
890 | 1. Let _parameterSym_ be the grammar symbol |FormalParameters[~Yield, +Await]|.
891 | 1. Let _fallbackProto_ be *"%AsyncFunction.prototype%"*.
892 | 1. Else,
893 | 1. Assert: _kind_ is ~asyncGenerator~.
894 | 1. Let _exprSym_ be the grammar symbol |AsyncGeneratorExpression|.
895 | 1. Let _bodySym_ be the grammar symbol |AsyncGeneratorBody|.
896 | 1. Let _parameterSym_ be the grammar symbol |FormalParameters[+Yield, +Await]|.
897 | 1. Let _fallbackProto_ be *"%AsyncGeneratorFunction.prototype%"*.
898 | 1. Let _argCount_ be the number of elements in _args_.
899 | 1. Let _P_ be the empty String.
900 | 1. If _argCount_ = 0, let _bodyArg_ be the empty String.
901 | 1. Else if _argCount_ = 1, let _bodyArg_ be _args_[0].
902 | 1. Else,
903 | 1. Assert: _argCount_ > 1.
904 | 1. Let _firstArg_ be _args_[0].
905 | 1. Set _P_ to ? ToString(_firstArg_).
906 | 1. Let _k_ be 1.
907 | 1. Repeat, while _k_ < _argCount_ - 1,
908 | 1. Let _nextArg_ be _args_[_k_].
909 | 1. Let _nextArgString_ be ? ToString(_nextArg_).
910 | 1. Set _P_ to the string-concatenation of _P_, *","* (a comma), and _nextArgString_.
911 | 1. Set _k_ to _k_ + 1.
912 | 1. Let _bodyArg_ be _args_[_k_].
913 | 1. Let _bodyString_ be the string-concatenation of 0x000A (LINE FEED), ? ToString(_bodyArg_), and 0x000A (LINE FEED).
914 | 1. Let _prefix_ be the prefix associated with _kind_ in .
915 | 1. Let _sourceString_ be the string-concatenation of _prefix_, *" anonymous("*, _P_, 0x000A (LINE FEED), *") {"*, _bodyString_, and *"}"*.
916 | 1. Let _sourceText_ be ! StringToCodePoints(_sourceString_).
917 | 1. Let _parameters_ be ParseText(! StringToCodePoints(_P_), _parameterSym_).
918 | 1. If _parameters_ is a List of errors, throw a *SyntaxError* exception.
919 | 1. Let _body_ be ParseText(! StringToCodePoints(_bodyString_), _bodySym_).
920 | 1. If _body_ is a List of errors, throw a *SyntaxError* exception.
921 | 1. NOTE: The parameters and body are parsed separately to ensure that each is valid alone. For example, `new Function("/*", "*/ ) {")` is not legal.
922 | 1. NOTE: If this step is reached, _sourceText_ must match _exprSym_ (although the reverse implication does not hold). The purpose of the next two steps is to enforce any Early Error rules which apply to _exprSym_ directly.
923 | 1. Let _expr_ be ParseText(_sourceText_, _exprSym_).
924 | 1. If _expr_ is a List of errors, throw a *SyntaxError* exception.
925 | 1. NOTE: The dynamic function must not contain an unbound topic reference `%`.)
926 | 1. If ContainsOuterTopic of _expr_ is *true*, throw a *SyntaxError* exception.
927 | 1. Let _proto_ be ? GetPrototypeFromConstructor(_newTarget_, _fallbackProto_).
928 | 1. Let _realmF_ be the current Realm Record.
929 | 1. Let _scope_ be _realmF_.[[GlobalEnv]].
930 | 1. Let _privateScope_ be *null*.
931 | 1. Let _F_ be ! OrdinaryFunctionCreate(_proto_, _sourceText_, _parameters_, _body_, ~non-lexical-this~, _scope_, _privateScope_).
932 | 1. Perform SetFunctionName(_F_, *"anonymous"*).
933 | 1. If _kind_ is ~generator~, then
934 | 1. Let _prototype_ be ! OrdinaryObjectCreate(%GeneratorFunction.prototype.prototype%).
935 | 1. Perform DefinePropertyOrThrow(_F_, *"prototype"*, PropertyDescriptor { [[Value]]: _prototype_, [[Writable]]: *true*, [[Enumerable]]: *false*, [[Configurable]]: *false* }).
936 | 1. Else if _kind_ is ~asyncGenerator~, then
937 | 1. Let _prototype_ be ! OrdinaryObjectCreate(%AsyncGeneratorFunction.prototype.prototype%).
938 | 1. Perform DefinePropertyOrThrow(_F_, *"prototype"*, PropertyDescriptor { [[Value]]: _prototype_, [[Writable]]: *true*, [[Enumerable]]: *false*, [[Configurable]]: *false* }).
939 | 1. Else if _kind_ is ~normal~, perform MakeConstructor(_F_).
940 | 1. NOTE: Functions whose _kind_ is ~async~ are not constructible and do not have a [[Construct]] internal method or a *"prototype"* property.
941 | 1. Return _F_.
942 |
943 |
944 |
945 |
946 |
947 |
948 |
--------------------------------------------------------------------------------