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 +> 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.
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=>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.
Piping the underlying promise/iterator instead of the declared output requires careful and repetitive boilerplate to compose an AsyncFunction
, GeneratorFunction
or AsyncGeneratorFunction
from other Function
s, AsyncFunction
s, GeneratorFunction
s and AsyncGeneratorFunction
s, 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.