Skip to content
J. S. Choi edited this page Mar 9, 2021 · 126 revisions

Overview

The threads are becoming unwieldy, so here's the current status of the pipeline proposals.


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, new object construction, etc.)
  • Able to await in 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(?)
x + 1 x |> (y=>y + 1) x |> (y=>y + 1) x |> ? + 1
[0, x] x |> (y=>[0, y]) x |> (y=>[0, y]) x |> [0, x]
{ 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. Proposal 0 is specifically for F# style only.

  • 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 await or yield expressions, 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 a special 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 await expressions with special extra syntax (|> await)
  • Cannot handle yield expressions, 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 “binding”, “placeholder”, and “parameterized” style. Proposal 2 is specifically for Hack style only, modulo some possible minor enhancements; Proposals 3 and 4 also have 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 # (see issue #91)
  • No tacit unary function calls, need to add (?) to their ends
  • Handles await and yield expressions 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
  • Placeholder token might be ? or % or @ or # (see issue #91)
    • Handles await and yield expressions without special extra syntax
  • 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 # (see issue #91)
    • Handles await and yield expressions without special extra syntax
  • 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 ., like console.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 into x |> f(?, a, b), x |> f(a, ?, b), x |> f(a, b, ?), or x |> 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;
Clone this wiki locally