Skip to content

explainer: Create § Relationships, other proposals #241

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Oct 11, 2021
206 changes: 132 additions & 74 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,45 @@ But with F# pipes, `value |> someFunction + 1` is **still valid syntax** –
it’ll just **fail late** at **runtime**,
because `someFunction + 1` isn’t callable.

### TC39 has rejected F# pipes multiple times
The pipe champion group has presented F# pipes for Stage 2 to TC39 **twice**.
It was **unsuccessful** in advancing to Stage 2 both times.
Both F# pipes (and [partial function application (PFA)][PFA syntax])
have run into strong pushback from multiple other TC39 representatives
due to various concerns. These have included:

* Memory performance concerns (e.g., [especially from browser-engine implementors][V8 pushback]),
* Syntax concerns about `await`.
* Concerns about encouraging ecosystem bifurcation/forking, etc.

[V8 pushback]: https://github.com/tc39/proposal-pipeline-operator/blob/main/HISTORY.md#2021-07

This pushback has occurred from **outside** the pipe champion group.
See [HISTORY.md][] for more information.

It is the pipe champion group’s belief that any pipe operator is better than none,
in order to [easily linearize deeply nested expressions](#why-a-pipe-operator)
without resorting to named variables.
Many members of the champion group believe that Hack pipes are slightly better than F# pipes,
and some members of the champion group believe that F# pipes are slightly better than Hack pipes.
But everyone in the champion group agrees that F# pipes have met with far too much resistance
to be able to pass TC39 in the foreseeable future.

To emphasize, it is likely that an attempt to switch from Hack pipes back to F# pipes
will result in TC39 never agreeing to any pipes at all.
[PFA syntax][] is similarly facing an uphill battle in TC39 (see [HISTORY.md][]).
Many members of the pipe champion group think this is unfortunate,
and they are willing to fight again **later** for an [F#-pipe split mix][split mix] and [PFA syntax][].
But there are quite a few representatives (including [browser-engine implementers][V8 pushback])
outside of the Pipe Champion Group
who are generally against encouraging [tacit programming][] (and [PFA syntax][]),
regardless of Hack pipes.

[HISTORY.md]: https://github.com/tc39/proposal-pipeline-operator/blob/main/HISTORY.md
[tacit programming]: https://en.wikipedia.org/wiki/Tacit_programming
[PFA syntax]: https://github.com/tc39/proposal-partial-application
[split mix]: #tacit-unary-function-application-syntax

## Description
(A [formal draft specification][specification] is available.)

Expand Down Expand Up @@ -824,50 +863,53 @@ context
[jquery/src/core/init.js]: https://github.com/jquery/jquery/blob/2.2-stable/src/core/init.js
[underscore.js]: https://underscorejs.org/docs/underscore-esm.html

## Possible future extensions
## Relationships with other proposals

### `Function` helpers
Hack pipes can and would coexist with the [`Function` helpers proposal][helpers],
including its `pipe` and `flow` functions.
These simple (and commonly downloaded) convenience functions
manipulate unary functions without extra syntax.

[helpers]: https://github.com/js-choi/proposal-function-helpers

[TC39 has rejected the F# pipe operator twice][rejected].
Given this reality, TC39 is considerably more likely to pass
`pipe` and `flow` helper functions than a similar syntactic operator.

[rejected]: #tc39-has-rejected-f-pipes-multiple-times

Standardized `pipe` and `flow` convenience functions
may also obviate some of the need for a F#-pipe infix operator.
(They would not preclude standardizing an equivalent operator later.
For example, TC39 standardized binary `**` even when `Math.pow` existed.)

### Partial-function-application syntax
Hack pipes can coexist with a syntax for **partial function application** (PFA).
There are two approaches with which they may coexist.

### Hack-pipe functions
If Hack pipes are added to JavaScript,
then they could also elegantly handle
**partial function application** in the future
The **first approach** is with an **eagerly** evaluated PFA syntax,
which has [already been proposed in proposal-partial-application][PFA syntax].
This eager PFA syntax would add an `…~(…)` operator.
The operator’s right-hand side would be a list of arguments,
each of which is an ordinary expression or a `?` placeholder.
Each consecutive `?` placeholder would represent another parameter.

Ordinary expressions would be evaluated **before** the function is created.
For example, `f~(g(), ?, h(), ?)` would evaluate `f`, then `g()`, then `h()`,
and *then* it would create a partially applied version of `f` with two arguments.

An optional number after `?` placeholder
would override the parameter’s position.
For example, `f~(?1, ?0)` would have two parameters but would switch them when calling `f`.

The **second approach** is with a **lazily** evaluated syntax.
This could be handled with an **extension to Hack pipes**,
with a syntax further inspired by
[Clojure’s `#(^1 ^2)` function literals][Clojure function literals].

There is **already** a [proposed special syntax
for partial function application (PFA) with `?` placeholders][PFA]
(abbreviated here as **`?`-PFA**).
Both `?`-PFA and Hack pipes address a **similar problem** –
binding values to **placeholder tokens** –
but they address it in different ways.

With **`?`-PFA**, `?` placeholders are valid
only directly within function-call expressions,
and **each consecutive** `?` placeholder in an expression
refers to a **different** argument **value**.
This is in contrast to **Hack pipes**,
in which every `^` token in an expression
refers to the **same value**.
`?`-PFA’s design integrates well with **F# pipes**,
rather than Hack pipes, but this could be changed.

[PFA]: https://github.com/tc39/proposal-partial-application/

| `?`-PFA with F# pipes | Hack pipes |
| -------------------------- | -------------------------- |
|`x \|> y=> y + 1` |`x \|> ^ + 1` |
|`x \|> f(?, 0)` |`x \|> f(^, 0)` |
|`a.map(x=> x + 1)` |`a.map(x=> x + 1)` |
|`a.map(f(?, 0))` |`a.map(x=> f(x, 0))` |
|`a.map(x=> x + x)` |`a.map(x=> x + x)` |
|`a.map(x=> f(x, x))` |`a.map(x=> f(x, x))` |
|`a.sort((x,y)=> x - y)` |`a.sort((x,y)=> x - y)` |
|`a.sort(f(?, ?, 0))` |`a.sort((x,y)=> f(x, y, 0))`|

The PFA proposal could instead **switch from `?` placeholders**
to **Hack-pipe topic references**.
It could do so by combining the Hack pipe `|>`
with the arrow function `=>`
into a **topic-function** operator `+>`,
[Clojure’s `#(%1 %2)` function literals][Clojure function literals].
It would do so by **combining** the Hack pipe `|>`
with the **arrow function** `=>`
into a **pipe-function** operator `+>`,
which would use the same general rules as `|>`.

`+>` would be a **prefix operator** that **creates a new function**,
Expand All @@ -881,36 +923,44 @@ And just as with `|>`, `+>` would require its body
to contain at least one topic reference
in order to be syntactically valid.

| `?`-PFA | Hack pipe functions |
| Eager PFA | Pipe functions |
| ---------------------------| -------------------------- |
|`a.map(f~(?, 0))` |`a.map(+> f(^, 0))` |
|`a.map(f~(?, ?, 0))` |`a.map(+> f(^0, ^1, 0))` |
|`a.map(x=> x + 1)` |`a.map(+> ^ + 1)` |
|`a.map(f(?, 0))` |`a.map(+> f(^, 0))` |
|`a.map(x=> x + x)` |`a.map(+> ^ + ^)` |
|`a.map(x=> f(x, x))` |`a.map(+> f(^, ^))` |
|`a.sort((x,y)=> x - y)` |`a.sort(+> ^0 - ^1)` |
|`a.sort(f(?, ?, 0))` |`a.sort(+> f(^0, ^1, 0))` |

Pipe functions would **avoid** the `?`-PFA syntax’s **[garden-path problem][]**.
When we read the expression **from left to right**,
the `+>` prefix operator makes it readily apparent
that the expression is **creating a new function** from `f`,
rather than **calling** `f` **immediately**.
In contrast, `?`-PFA would require us
to **check every function call for a `?` placeholder**
in order to determine whether it is actually an immediate function call.

[garden-path problem]: https://en.wikipedia.org/wiki/Garden-path_sentence

In addition, pipe functions wouldn’t help only partial function application.
Their **flexibility** would allow for **partial expression application**,
concisely creating functions from other kinds of expressions
in ways that would not be possible with `?`-PFA.

| `?`-PFA | Hack pipe functions |
| -------------------------- | -------------------------- |
|`a.map(x=> x + 1)` |`a.map(+> ^ + 1)` |
|`a.map(x=> x + x)` |`a.map(+> ^ + ^)` |
|`a.sort((x,y)=> x - y)` |`a.sort(+> ^0 - ^1)` |

In contrast to the [eagerly evaluated PFA syntax][PFA syntax],
topic functions would **lazily** evaluate its arguments,
just like how an arrow function would.

For example, `+> f(g(), ^0, h(), ^1)` would evaluate `f`,
and then it would create an arrow function that closes over `g` and `h`.
The created function would **not** evaluate `g()` or `h()`
until the every time the created function is called.

No matter the approach taken, Hack pipes could coexist with PFA.

### Eventual sending / pipelining
Despite sharing the word “pipe” in their name,
the pipe operator and the [eventual-send proposal][]’s remote-object pipelines
are orthogonal and independent.
They can coexist and even work together.

```js
const fileP = E(
E(target).openDirectory(dirName)
).openFile(fileName);

const fileP = target
|> E(^).openDirectory(dirName)
|> E(^).openFile(fileName);
```

[eventual-send proposal]: https://github.com/tc39/proposal-eventual-send/

## Possible future extensions

### Hack-pipe syntax for `if`, `catch`, and `for`–`of`
Many **`if`, `catch`, and `for` statements** could become pithier
Expand All @@ -933,18 +983,26 @@ much in the way `?.` is useful for optional method calls.
For example, `value |> (^ == null ? ^ : await foo(^) |> (^ == null ? ^ : ^ + 1))`\
would be equivalent to `value |?> await foo(^) |?> ^ + 1`.

### Tacit unary function application
**Tacit unary function application** – that is, F# pipes –
could still be added to the language with **another pipe operator** `|>>` –
similarly to how [Clojure has multiple pipe macros][Clojure pipes]
`->`, `->>`, and `as->`.
### Tacit unary function application syntax
**Syntax** for **tacit unary function application** – that is, the F# pipe operator –
has been [rejected twice by TC39][rejected].
However, they could still eventually be added to the language in two ways.

[Clojure pipes]: https://clojure.org/guides/threading_macros
First, it can be added as a convenience function `Function.pipe`.
This is what the [function-helpers proposal][helpers] proposes.
`Function.pipe` may obviate much of the need for an F#-pipe operator,
while still not closing off the possibility of an F#-pipe operator.

Secondly, it can be added as **another pipe operator** `|>>` –
similarly to how [Clojure has multiple pipe macros][Clojure pipes]
`->`, `->>`, and `as->`.\
For example, `value |> ^ + 1 |>> f |> g(^, 0)`\
would mean `value |> ^ + 1 |> f(^) |> g(^, 0)`.

[Clojure pipes]: https://clojure.org/guides/threading_macros

There was an [informal proposal for such a **split mix** of two pipe operators][split mix],
which was set aside in favor of single-operator proposals.
This split mix might return as a proposal after Hack pipes.

[split mix]: https://github.com/tc39/proposal-pipeline-operator/wiki#proposal-3-split-mix