+> for function composition

Summary

A proposed function composition operator that allows terse and intuitive composition, in execution order, from and to any of the 4 available function types: Function, AsyncFunction, GeneratorFunction and AsyncGeneratorFunction.

Purpose

To significantly reduce code complexity and minimize the chances of bugs (coding errors) in the problem space, and to add a whole new dimension of expressive power to the language.

Usage

The statement:

const doubleThenSquareThenHalf = value=>half(square(double(value)))

is rewritable as:

const doubleThenSquareThenHalf = double +> square +> half

Introducing an AsyncFunction produces an AsyncFunction that pipes its expressed return value to subsequent functions, e.g.:

const doubleThenSquareThenHalfAsync = double +> squareAsync +> half

Introducting a GeneratorFunction produces a GeneratorFunction that pipes each yielded value to subsequent functions, e.g.:

const randomBetween0And100Generator = randomBetween0And1Generator +> multiplyBy100

Introducing an AsyncFunction and a GeneratorFunction, and/or an AsyncGeneratorFunction, produces an AsyncGeneratorFunction that in each case pipes its expressed return value or each expressed yielded value to subsequent functions, e.g.:

const nextRouteAsyncGenerator = nextLocationGenerator +> calculateRouteAsync //GeneratorFunction +> AsyncFunction
const nextRouteAsyncGenerator = nextLocationAsyncGenerator +> calculateRoute //AsyncGeneratorFunction +> Function

It would be usable to tersely express the following:

const switchOnEngineThenDrive = ()=>{switchOnEngine(); drive()}

as:

const switchOnEngineThenDrive = switchOnEngine +> drive

Although it evaluates to drive(switchOnEngine()) upon execution, it behaves the same as sequential execution for all intents and purposes, in cases of no-args functions.

As an analogy for how x = x + y is expressable as x += y, the following:

x = x +> y

would be expressable as:

x +>= y

e.g. for composing functions in a loop.

Why +>?

To express accumulation via the + and function ordering via the >, and so as not to conflict with the pipeline-operator proposal here: https://github.com/tc39/proposal-pipeline-operator which has prior art from other languages. Discussion: tc39/proposal-pipeline-operator#50

Why treat AsyncFunction, GeneratorFunction and AsyncGeneratorFunction differently than their promise/iterator returning Function equivalents? They are the same in all other contexts!

1. Semantic expectation.

For

async input=>output

I semantically expect output to be piped to the next function in the chain.

For

function*(){
    yield output;
}

I semantically expect output to be utilized.

2. Less chance of bugs in the problem space

Piping the underlying promise/iterator instead of the declared output requires careful and repetitive boilerplate to compose an AsyncFunction, GeneratorFunction or AsyncGeneratorFunction from other Functions, AsyncFunctions, GeneratorFunctions and AsyncGeneratorFunctions, thereby causing a greater surface area for mistakes and bugs in the problem space.

3. Smaller learning curve for async and generator function composition

For the same reasons, it is possible to compose async and generator functions without necessarily even knowing anything about promises and iterators. (For example, C# uses async and await but without promises, but the usage pattern is the same).

4. Piping promises and iterators would be supported otherwise anyway

Piping explicitly returned promises/iterators in a declared Function would work as expected anyway, so it is unclear what practical advantage there could possibly be of piping non-explicitly-returned promises/iterators, in the cases such as the examples in 1., instead of the declared output.

Isn't overloading an operator with different types producing different expression results a bad thing?

There is precedent. myNumber + 'myString' has been and continues to be used perfectly intuitively since inception. The proposed expression results are intuitive based on the arguments supplied. It is necessary to allow composition between different function types, as the examples show, so it makes sense to allow them to use the same operator.