Skip to content
tjjfv edited this page Mar 10, 2019 · 89 revisions

Overview

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


Goals

  • Requirement: Easy composition of functions for immediate invocation
  • Requirement: Usable with any function arity
  • Requirement: Able to await in the middle of a pipeline
  • Requirement: Method calls without binding
  • Nice to have: Other arbitrary expressions (arithmetic operations, array/object literals, new constructed objects, etc.)

Summary of proposals’ behavior

Proposal Original/minimal proposal (F# only) Proposal 1 (F# only + await) Proposal 4 (smart pipelines)
x |> o.p o.p(x) o.p(x) o.p(x)
x |> (o.p) o.p(x) o.p(x) Early Syntax Error: Topic-style pipeline needs topic reference #. Use
x |> o.p or
x |> (o.p)(#).
x |> o.p(y) o.p(y)(x) o.p(y)(x) Early Syntax Error: Topic-style pipeline needs topic reference #. Use
x |> f(y, #),
x |> f(#, y), or
x |> f(y)(#).
x |> await Early Syntax Error: await not supported await x: await the value x Early Syntax Error: Topic-style pipeline needs topic reference #. Use
x |> await #.
x |> (await) (await)(x) – Syntax Error: Unexpected ) (await)(x) – Syntax Error: Unexpected ) Early Syntax Error: Topic-style pipeline needs topic reference #. Use
x |> await #.
x |> await o.p Syntax Error: await not supported (await x) o.p – Syntax Error: Unexpected o.p Without Feature BA:
Early Syntax Error: Topic-style pipeline needs topic reference #. Use
x |> await o.p(#) or
x |> (await o.p)(#).
With Feature BA:
await o.p(x)
x |> (await o.p) Syntax Error: await not supported (await o.p)(x) Early Syntax Error: Topic-style pipeline needs topic reference #. Use
x |> await o.p(#) or
x |> (await o.p)(#).
x |> await o.p(y) Syntax Error: await not supported (await o.p(y))(x) Early Syntax Error: Topic-style pipeline needs topic reference #. Use
x |> await o.p(y, #),
x |> await o.p(#, y),
x |> await o.p(y)(#),
x |> (await o.p(y))(#),
x |> (await o.p)(#), or
x |> await #.
x |> new Syntax Error Syntax Error Syntax Error
x |> (new) (new)(x) – Syntax Error: Unexpected ) (new)(x) – Syntax Error: Unexpected ) (new)(x) – Syntax Error: Unexpected )
x |> new o.p TypeError: new o.p is not a function; it is an instance of o.p. TypeError: new o.p is not a function; it is an instance of o.p. Without Feature BC:
Early Syntax Error: Topic-style pipeline needs topic reference #. Use
x |> await o.p(#) or
x |> (await o.p)(#).
With Feature BC:
new o.p(x)
x |> (new o.p) TypeError: (new o.p) is not a function; it is an instance of o.p. TypeError: (new o.p) is not a function; it is an instance of o.p. Early Syntax Error: Topic-style pipeline needs topic reference #. Use
x |> await o.p(#) or
x |> (await o.p)(#).
x |> new o.p(y) TypeError: (new o.p) is not a function; it is an instance of o.p. TypeError: (new o.p) is not a function; it is an instance of o.p. Early Syntax Error: Topic-style pipeline needs topic reference #. Use
x |> new o.p(y, #),
x |> new o.p(#, y),
x |> new o.p(y)(#),
x |> (new o.p(y))(#),
x |> (new o.p)(#), or
x |> new #.
x |> () => y Syntax Error: Unexpected =>. Use
x |> (() => y) instead
Undecided Early Syntax Error: Unexpected =>. Use
x |> (() => y)(#) or
x |> (() => #).
x |> (() => y) (() => y)(x) (() => y)(x) Early Syntax Error: Topic-style pipeline needs topic reference #. Use
x |> (() => y)(#) or
x |> (() => #).

Proposal 0: Original Minimal Proposal

The F-Sharp (F# / 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-Sharp style only. Proposal 1 also includes F-Sharp style only but also includes a special await form. Proposal 3 and Proposal 4 also include F-Sharp style but mix it with Hack style.

Characteristics:

  • Potentially the conceptually simplest proposal
  • Only sync unary function calls are handled
  • Does not handle async calls (that is, await)
  • Makes unary function calls especially terse (including for curried functions), but not ergonomic (without an added solution) for anything else: object/array literals, arithmetic operations, yield/yield * expressions

Current debates:

Proposal 1: F-Sharp Style Only with await

The F-Sharp (F# / 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-Sharp style only along with a special syntax for await. [Proposal 0] is similar except it forbids await in its RHS. Proposal 3 and Proposal 4 also include F-Sharp style but mix it with Hack style, and they also handle await using that Hack style rather than a special case.

// 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))

// Arbitrary 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
  |> (a => pickEveryN(a, 2))
  |> (a => a.filter(...))
  |> makeQuery
  |> (a => readDB(a, config))
  |> await
  |> extractRemoteUrl
  |> fetch
  |> await
  |> parse
  |> console.log;

Characteristics:

  • Offers a special bare |> await form as a solution for async
  • May or may not require arrow function with parentheses
  • Makes unary and curried functions especially terse, but not ergonomic (without an added solution) for anything else: object/array literals, arithmetic operations, yield/yield * expressions

Current debates:

  • Are arrow functions too verbose for 2+ arity functions?
  • Are arrow functions too verbose for arbitrary expressions?
    • No currently proposed solutions except pre-creating functions for those expressions.
  • Just like with await, arrow functions cannot handle yield and yield *. Should F-sharp style also have syntax for yield and yield * expressions? See issue #90.

Proposal 2: Hack Style Only

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.

These examples use # as the placeholder for illustration only; what exact token it is is under debate. Previous versions of this page also used instead of # for illustrative purposes. See issue #91.

// 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))

// 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
  |> pickEveryN(#, 2)
  |> #.filter(...)
  |> makeQuery(#)
  |> await readDB(#, config)
  |> extractRemoteUrl(#)
  |> fetch(#)
  |> await #
  |> parse(#)
  |> console.log(#);

Characteristics:

  • Fully explicit (no implicit invocations)
  • Works great for 2+ arity functions and arbitrary expressions (including await, yield, and yield *)
  • Use with unary functions is not bad, but not the best either
  • Not as ergonomic as F-sharp style for curried functions
  • Depending on chosen placeholder token (issue #91), may or may not be incompatible with the partial application proposal

Current debates:

Proposal 3: Split Mix

Since both F-sharp 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.

Split mix proposes:

  • One operator for implicit invocation (F-sharp style), e.g., |>
  • One operator for explicit placeholders (Hack style), e.g., |: or |>>
// Basic Usage
x |> f     //-->   Syntax Error
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 (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
  |: pickEveryN(#, 2)
  |: #.filter(...)
  |> makeQuery
  |: await readDB(#, config)
  |> extractRemoteUrl
  |> fetch
  |: await #
  |> parse
  |> console.log;

Characteristics:

Current debates:

  • Do we really need to add two operators for pipelining?
  • What should the operators look like?
  • Same issue for placeholder token as in Proposal 2 (see issue #91).
  • Because Hack style could take care of await, yield, and yield *, would F-sharp style still need to have special cases for |> await, etc.?

Proposal 4: Smart Mix

Smart Pipelines Explainer
Smart Pipelines Specification


  • Combine features of the two main proposals into a single operator.
  • Topic style: Hack behavior by default (requires use of a #, otherwise early syntax error)
  • Bare style: F-sharp behavior when body is a “simple” reference: one or more identifiers separated by ., like console.log; no parentheses, brackets, braces, or other operators, otherwise must be topic style
  • Early syntax error when any non-bare (i.e., topic-style) body does not contain a placeholder, to ensure easy distinguishability between Hack mode and F-sharp mode (e.g., x |> f(a, b) is invalid and must be distinguished as x |> f(#, a, b), x |> f(a, #, b), x |> f(a, b, #), or x |> f(a, b)(#).
// 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
  |> pickEveryN(#, 2)
  |> #.filter($ => $ > 0)
  |> makeQuery
  |> await readDB(#, config)
  |> extractRemoteUrl
  |> await fetch(#)
  |> parse
  |> new User.Result(#)
  |> console.log;

Characteristics:

  • Smart Pipelines Explainer
  • Smart Pipelines Specification
  • All benefits of two main proposals while avoiding their downsides
  • Only one operator required
  • Method binding naturally occurs: … |> document.querySelector |> … is same as … |> document.querySelector(#) |> …
  • Does not need additional partial application syntax
  • A single extension could address partial application (+> f(#, 5, 2)), function composition (+> f |> g |> h(#, 1) |> # + 1), and many kinds of method extraction +> console.log, subsuming all those use cases
  • An extension to bare style (Feature BC) would enable tacit construction of new objects: x |> new Foo would be x |> new Foo(#)new Foo(x)
  • Another extension to bare style (Feature BA) would enable tacit awaited function calls: x |> await bar is await bar(x) rather than (await bar)(x)
  • Other extensions can integrate with pattern matching, block parameters, lifted pipelines, error catching, for loops
  • Leaving out the # in a non-bare-style such as x |> f(1, 2) is an early Syntax Error, preventing garden-path syntax, preventing infinitely nested lookahead, and preventing accidental misinterpretation of a function call as an expression
  • Auto-curried function-returning functions require a (#) at the end, e.g., value |> filter(pred)(#)
  • Discussed in issue #100; first proposed in issue #75 and issue #89

Current debates:

  • Is this behavior too confusing? Can this be mitigated with a simple-enough syntax? How may the syntax be designed to avoid footguns?
  • Same issue for placeholder token as in Proposal 2 (issue #91).
  • Same issue for unnecessity of special casing the F-sharp style |> await, |> yield, and |> yield * as in Proposal 3.

Legacy headings

Proposal 1: F-Sharp Style

This heading is the old name of Proposal 1: F-sharp Style Only.

Proposal 1: F-Sharp Style Only

This heading is the old name of Proposal 1: F-sharp Style Only with await.

Proposal 2: Hack Style

This heading is the old name of Proposal 2: Hack Style Only.

Clone this wiki locally
You can’t perform that action at this time.