└── README.md /README.md: -------------------------------------------------------------------------------- 1 | # `+>` for function composition 2 | 3 | ## Summary 4 | 5 | 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`. 6 | 7 | ## Purpose 8 | 9 | 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. 10 | 11 | ## Usage 12 | 13 | The statement: 14 | 15 | ```javascript 16 | const doubleThenSquareThenHalf = value=>half(square(double(value))) 17 | ``` 18 | 19 | is rewritable as: 20 | 21 | ```javascript 22 | const doubleThenSquareThenHalf = double +> square +> half 23 | ``` 24 | 25 | Introducing an `AsyncFunction` produces an `AsyncFunction` that pipes its expressed return value to subsequent functions, e.g.: 26 | 27 | ```javascript 28 | const doubleThenSquareThenHalfAsync = double +> squareAsync +> half 29 | ``` 30 | 31 | Introducting a `GeneratorFunction` produces a `GeneratorFunction` that pipes each yielded value to subsequent functions, e.g.: 32 | 33 | ```javascript 34 | const randomBetween0And100Generator = randomBetween0And1Generator +> multiplyBy100 35 | ``` 36 | 37 | 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.: 38 | 39 | ```javascript 40 | const nextRouteAsyncGenerator = nextLocationGenerator +> calculateRouteAsync //GeneratorFunction +> AsyncFunction 41 | ``` 42 | 43 | ```javascript 44 | const nextRouteAsyncGenerator = nextLocationAsyncGenerator +> calculateRoute //AsyncGeneratorFunction +> Function 45 | ``` 46 | 47 | It would be usable to tersely express the following: 48 | 49 | ```javascript 50 | const switchOnEngineThenDrive = ()=>{switchOnEngine(); drive()} 51 | ``` 52 | 53 | as: 54 | 55 | ```javascript 56 | const switchOnEngineThenDrive = switchOnEngine +> drive 57 | ``` 58 | 59 | 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. 60 | 61 | As an analogy for how `x = x + y` is expressable as `x += y`, the following: 62 | 63 | ```javascript 64 | x = x +> y 65 | ``` 66 | 67 | would be expressable as: 68 | 69 | ```javascript 70 | x +>= y 71 | ``` 72 | 73 | e.g. for composing functions in a loop. 74 | 75 | ## Why `+>`? 76 | 77 | 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: https://github.com/tc39/proposal-pipeline-operator/issues/50 78 | 79 | ## Why treat `AsyncFunction`, `GeneratorFunction` and `AsyncGeneratorFunction` differently than their promise/iterator returning `Function` equivalents? They are the same in all other contexts! 80 | 81 | ### 1. Semantic expectation. 82 | 83 | For 84 | ```js 85 | async input=>output 86 | ``` 87 | 88 | I semantically expect `output` to be piped to the next function in the chain. 89 | 90 | For 91 | ```js 92 | function*(){ 93 | yield output; 94 | } 95 | ``` 96 | 97 | I semantically expect `output` to be utilized. 98 | 99 | ### 2. Less chance of bugs in the problem space 100 | 101 | 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. 102 | 103 | ### 3. Smaller learning curve for async and generator function composition 104 | 105 | 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). 106 | 107 | ### 4. Piping promises and iterators would be supported otherwise anyway 108 | 109 | 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.](https://github.com/TheNavigateur/proposal-pipeline-operator-for-function-composition/blob/master/README.md#1-semantic-expectation), instead of the declared output. 110 | 111 | ## Isn't overloading an operator with different types producing different expression results a bad thing? 112 | 113 | 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. 114 | --------------------------------------------------------------------------------