And a native operator can be optimized just about the same amount as a built-in function. So maybe add the function instead of the operator to the language?
One issue with a `pipe` function is that it is an arity of N. That makes it almost impossible to optimize for all the edge cases. In contrast, the pipe operator is always fixed at an arity of 2. Implementing a static Function.pipe in terms of the primitive operator becomes easy enough. Functional languages have played with both the function and the operator, but they keep coming back to the operator because it's more readable.
//implementation of your pipe in terms of the (potential) pipe operator
Function.pipe = (fn, ...args) => {
//let's use a pseudo Duff's device
switch (args.length) {
case 0: return fn;
case 1: return fn |> args[0];
case 2: return fn |> args[0] |> args[1];
case 3: return fn |> args[0] |> args[1] |> args[2];
case 4: return fn |> args[0] |> args[1] |> args[2] |> args[3];
case 5: return fn |> args[0] |> args[1] |> args[2] |> args[3] |> args[4];
case 6: return fn |> args[0] |> args[1] |> args[2] |> args[3] |> args[4] |> args[5];
case 7: return fn |> args[0] |> args[1] |> args[2] |> args[3] |> args[4] |> args[5] |> args[6];
case 8: return fn |> args[0] |> args[1] |> args[2] |> args[3] |> args[4] |> args[5] |> args[6] |> args[7];
default:
var mod = args.length >> 3; //div by 8
var rem = -8 * mod + args.length; //mult add
fn = Function.pipe(fn, ...args.slice(0, rem));
while (rem < args.length) {
fn = fn |> args[rem++] |> args[rem++] |> args[rem++] |> args[rem++] |> args[rem++] |> args[rem++] |> args[rem++] |> args[rem++];
}
return fn;
}
};
It's a little off topic, but I'd love to see the addition of well-known symbols for all the operators to allow custom overloading. Lua does operator overloading with metatables. No reason JS can't too. At that point, pipe, compose, and even spaceship become much more useful.
var x = {
foo: [1,2,3],
[Symbol.add](rightHandSide) {//Binary
return this.foo.concat(rightHandSide);
},
[Symbol.incr]() {//Unary
this.foo = this.foo.map(x => x += 1);
return this.foo;
}
}
//default to standard operator if
//if left side is primitive or if
//left side has no matching Symbol
x + 3; //same as x[Symbol.add](3)
[0].concat(x++); //=> [0,1,2,3]
//same as [0].concat(x); x[Symbol.incr]();
[0, 1].concat(++x); //=> [0, 1, 2, 3, 4]
//same as x[Symbol.incr](); [0, 1].concat(x);
You can also replace all `a |> b` with a fixed arity `pipe2(a, b)` in your code and again, no new operator needed. Anwyay, I don't think such optimization issues are enough to justify adding this operator to JavaScript. In other functional languages it may make sense, because operators work differently there (e.g. they are interchangeable with functions, there is support for overloading, etc.).
> I'd love to see the addition of well-known symbols for all the operators to allow custom overloading.
Sure, if we had support for first-class overloadable operators then a proposal to add a standard operator like `|>` would make sense. But we don't, so a better one would be to add `Function.pipe` (simple, fits into the language better) or allow operator overloading, etc. (also may be reasonable, but it's a major change in the language).