Home
This is the wiki, which just about anyone can edit.
Please check the sidebar for subpages, such as the topic-token bikeshedding page and tooling status page.
Other useful documents you may want to read include:
Overview of previous proposals
The following is an archive of a previous wiki home page that summarized our public discussions.
For more detailed information on the history of the pipe proposal, please see HISTORY.md.
- Proposal 0: Minimal F# style (explainer #0, specification #0)
-
Proposal 1: F# style with
await(explainer #1, spec #1) - Proposal 2: Hack style (explainer #2, spec #2)
Proposal 3: Split mixProposal 4: Smart mix (explainer #4, specification #4)
- 2021-03 Tab Atkins’ thoughts about why pipe operator is important and about F# style vs. Hack style vs. smart mix
- 2021-03 TC39 presentation
- 2018-03 TC39 presentation
Goals
- Easy composition of functions for immediate invocation
- Support for any function arity, not just unary functions
- Easy composition of other expressions (method calls, math, array/object literals,
newobject construction, etc.) - Able to
awaitin the middle of a pipeline - Method calls without binding
- Avoiding accidental footguns that would result in runtime errors
Summary of proposals’ behavior
| Original expression | Proposal 0 (minimal F#) | Proposal 1 (F# pipes + await) |
Proposal 2 (Hack pipes) |
|---|---|---|---|
o.m(x) |
x |> o.m |
x |> o.m |
x |> o.m(^) |
o.m(0, x) |
x |> y=>o.m(0, y) |
x |> y=>o.m(0, y) |
x |> o.m(0, ^) |
new o.m(x) |
x |> y=>new o.m(y) |
x |> y=>new o.m(y) |
x |> new o.m(^) |
o[x] |
x |> y=>o[y] |
x |> y=>o[y] |
x |> o[^] |
x[i] |
x |> y=>y[i] |
x |> y=>y[i] |
x |> ^[i] |
x + 1 |
x |> y=>y + 1 |
x |> y=>y + 1 |
x |> ^ + 1 |
[0, x] |
x |> y=>[0, y] |
x |> y=>[0, y] |
x |> [0, ^] |
{ key: x } |
x |> y=>({ key: y }) |
x |> y=>({ key: y }) |
x |> { key: ^ } |
await o.m(x) |
Not supported | x |> o.m |> await |
x |> await o.m(^) |
yield o.m(x) |
Not supported | Not supported | x |> (yield o.m(^)) |
Proposal 0: Minimal F# style
Minimal-F#-style explainer
Minimal-F#-style explainer
The F# (F-sharp / F♯) style |> invokes the right-hand side with the evaluated result of the left. It has also been called “implicit call”, “tacit” and “point-free” style.
- Unary function calls are very terse
- Anything else must be wrapped in an arrow function
- Arrow functions might or might not have to be wrapped in parentheses
- Cannot handle
awaitoryieldexpressions, which are scoped to their innermost function.
Proposal 1: F# style with await syntax
F#-style-with-await explainer
F#-style-with-await specification
The F# (F-sharp / F♯) style |> invokes the right-hand side with the evaluated result of the left. It has also been called “implicit call”, “tacit” and “point-free” style. Proposal 1 is specifically for F# style only along with special extra syntax for await. Proposal 0 is similar except it forbids await in its RHS.
- Unary function calls are very terse
- Anything else must be wrapped in an arrow function
- Arrow functions might or might not have to be wrapped in parentheses
- Handles
awaitexpressions with special extra syntax (|> await) - Cannot handle
yieldexpressions, which are scoped to their innermost function.
// Basic Usage x |> f //--> f(x) x |> f(y) //--> f(y)(x) // 2+ Arity Usage x |> (a => f(a,10)) //--> f(x,10) // Async Solution x |> f |> await //--> await f(x) x |> f |> await |> g //--> g(await f(x)) // Other Expressions f(x) |> (a => a.data) //--> f(x).data f(x) |> (a => a[a.length-1]) //--> let temp=f(x), temp[temp.length-1] f(x) |> (a => ({ result: a })) //--> { result: f(x) } // Complex example anArray => (anArray |> a => pickEveryN(a, 2) |> a => a.filter(...) |> makeQuery |> a => readDB(a, config) |> await |> extractRemoteUrl |> fetch |> await |> parse |> console.log );
Proposal 2: Hack style
Hack-style explainer
Hack-style specification
The Hack style |> evaluates the left-hand side and assigns it to a temporary binding scoped to the right-hand side. First proposed in issue #84. It has also been called “topic”, “binding”, “placeholder”, and “parameterized” style.
- Pipe to any expression using an explicit placeholder token
^ - Placeholder token can go where any normal variable can go
- Placeholder token might be
?or^or@or#(see issue #91) - No tacit unary function calls, need to add
(^)to their ends - Handles
awaitandyieldexpressions without special extra syntax - Forward compatible with both split mix (Proposal 3) and smart mix (Proposal 4)
Tab Atkins explains why he prefers Hack style.
// Basic Usage x |> f(^) //--> f(x) x |> f(y)(^) //--> f(y)(x) x |> f //--> Syntax Error // 2+ Arity Usage x |> f(^,10) //--> f(x,10) // Async Solution (Note this would not require special casing) x |> await f(^) //--> await f(x) x |> await f(^) |> g(^) //--> g(await f(x)) // Other Expressions f(x) |> ^.data //--> f(x).data f(x) |> ^[^.length-1] //--> let temp=f(x), temp[temp.length-1] f(x) |> { result: ^ } //--> { result: f(x) } // Complex example anArray => anArray |> pickEveryN(^, 2) |> ^.filter(...) |> makeQuery(^) |> await readDB(^, config) |> extractRemoteUrl(^) |> await fetch(^) |> parse(^) |> console.log(^);
Proposal 3: Split mix
Since both F# and Hack style proposals have desirable properties, it’s worth considering proposals that mix them together in a cohesive manner. Discussed in issue #89; previously discussed in issue #75 and issue #84.
- Requires browser vendors to agree to implement two similar pipe operators, so nobody is currently backing this proposal
- One operator for expressions with placeholder tokens (Hack style), e.g.,
|>or|:- Pipe to any expression using an explicit placeholder token
^ - Placeholder token can go where any normal variable can go
- Pipe to any expression using an explicit placeholder token
- Placeholder token might be
?or^or@or#(see issue #91)- Handles
awaitandyieldexpressions without special extra syntax
- Handles
- One operator for tacit unary function calls (F# style), e.g.,
|>>or|>- Unary function calls are very terse
// Basic Usage x |>> f //--> f(x) x |> f(^) //--> f(x) x |>> f(y) //--> f(y)(x) x |> f(y)(^) //--> Syntax Error // 2+ Arity Usage x |> f(^, 10) //--> f(x,10) // Async solution (does not require special casing) x |> await f(^) //--> await f(x) x |> await f(^) |> g //--> g(await f(x)) // Other expressions f(x) |> ^.data //--> f(x).data f(x) |> ^[^.length-1] //--> let temp=f(x), temp[temp.length-1] f(x) |> { result: ^ } //--> { result: f(x) } // Complex example anArray => anArray |> pickEveryN(^, 2) |> ^.filter(...) |>> makeQuery |> await readDB(^, config) |>> extractRemoteUrl |>> await fetch(^) |>> parse |>> console.log;
Proposal 4: Smart mix
Smart-mix explainer
Smart-mix specification
- This proposal was withdrawn in favor of Hack style, which is forward compatible with this proposal
- Combines features of the two main proposals into a single operator
- Topic/Hack style:
- Pipe to any expression using an explicit placeholder token
^ - Placeholder token can go where any normal variable can go
- Placeholder token might be
^or?or%or@or#(see issue #91) - Handles
awaitandyieldexpressions without special extra syntax
- Pipe to any expression using an explicit placeholder token
- Tacit/bare/F# style:
- Unary function calls are very terse if a specific syntax
- The expression must be a “simple” reference: one or more identifiers separated by
., likeconsole.log - The cannot contain parentheses, brackets, braces, the placeholder token, or other operators
- Syntax error when body of pipeline does not match either style
- Ensures easy distinguishability between topic mode and F# mode
-
x |> f(a, b)is invalid and must be disambiguated intox |> f(^, a, b),x |> f(a, ^, b),x |> f(a, b, ^), orx |> f(a, b)(^)
- If body of pipeline is currently written in F# style, and you realize you want to do something else to it, you must shift it to Hack style;
it’s a compile-time syntax error if you forget
// Basic Usage x |> f //--> f(x) x |> f(^) //--> f(x) // 2+ Arity Usage x |> f(y) //--> Syntax Error x |> f(y, ^) //--> f(y, x) x |> f(^, y) //--> f(x, y) x |> f(y)(^) //--> f(y)(x) // Async Solution (Note this would not require special casing) x |> await f(^) //--> await f(x) x |> await f(^) |> g //--> g(await f(x)) // Arbitrary Expressions f(x) |> ^.data //--> f(x).data f(x) |> ^[^.length-1] //--> let temp=f(x), temp[temp.length-1] f(x) |> { result: ^ } //--> { result: f(x) } // Complex Example anArray => anArray |> pickEveryN(^, 2) |> ^.filter($ => $ > 0) |> makeQuery |> await readDB(^, config) |> extractRemoteUrl |> await fetch(^) |> parse |> new User.Result(^) |> console.log;