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.
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.
The statement:
const doubleThenSquareThenHalf = value=>half(square(double(value)))is rewritable as:
const doubleThenSquareThenHalf = double +> square +> halfIntroducing an AsyncFunction produces an AsyncFunction that pipes its expressed return value to subsequent functions, e.g.:
const doubleThenSquareThenHalfAsync = double +> squareAsync +> halfIntroducting a GeneratorFunction produces a GeneratorFunction that pipes each yielded value to subsequent functions, e.g.:
const randomBetween0And100Generator = randomBetween0And1Generator +> multiplyBy100Introducing 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 +> AsyncFunctionconst nextRouteAsyncGenerator = nextLocationAsyncGenerator +> calculateRoute //AsyncGeneratorFunction +> FunctionIt would be usable to tersely express the following:
const switchOnEngineThenDrive = ()=>{switchOnEngine(); drive()}as:
const switchOnEngineThenDrive = switchOnEngine +> driveAlthough 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 +> ywould be expressable as:
x +>= ye.g. for composing functions in a loop.
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!
For
async input=>outputI semantically expect output to be piped to the next function in the chain.
For
function*(){ yield output; }I semantically expect output to be utilized.
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.
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).
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.