7 ms·
I wonder if there is really a need for this operator. Compare 2 |> square |> increment |> square and pipe(2, square, increment, square) where `pipe` is
by djedr 9y ago
I wonder if there is really a need for this operator.
Compare
2 |> square |> increment |> square
and
pipe(2, square, increment, square)
where `pipe` is defined as
const pipe = (...args) => args.reduce((prev, curr) => curr(prev));
So given that what would be a strong argument for introducing the operator to JavaScript?
- seanwilson 9y agoIs there a good way to see the intermediate results at each stage in the pipe when things go wrong? Pipes are concise but perhaps harder to debug depending on what tools you have.
- marksomnian 9y agofunction tee(arg) { console.log(arg); return arg; } 2 |> square |> tee |> increment |> square;
- throwanem 9y agoOr even, using another cute toy from the article: const tee = arg => console.log(arg), arg;
- seanwilson 9y agoSo I'm comparing pipe usage to this e.g. const result1 = square(2); const result2 = increment(result1); const result3 = square(result2); With the above, I can add in a breakpoint and inspect what's going on at each stage with access to other debugging tools. Log statements don't seem nearly as good as this. The above is nice as well in that you can give intuitive names to the intermediate results.
- djedr 9y agoIf you need names for intermediate results, etc. then you don't want the pipeline operator/function. In such cases the things you say are true. But when you wan't to be more concise, with the operator/function, you can. BTW A nice feature of a code editor would be if you could magically switch back and forth between code with intermediate named variables and pipelined version of the same code, e.g. by selecting the code and invoking an option.
- deleted 9y ago[deleted]
- djedr 9y agoYou can define a debug version: const pipe_debug = (...args) => args.reduce((prev, curr) => { console.log('pipe debug; curr:', curr, ', prev:', prev) return curr(prev) }) and when you want to see intermediate results of a `pipe` call, you append `_debug` to the name.
- philipwhiuk 9y agoReadability and not having to define a function / include a dependency of common functions (see leftpad).
- djedr 9y agoReadability I would argue is a matter of taste here, so not a very strong argument. As for the second thing, I'd agree, but instead of introducing an operator, we could make the helper a built-in feature.
- ams6110 9y agoYeah readability is largely about what you're used to. I find the nested function calls more readable, as I've used that notation for decades going all the way back to high school math.
- fenomas 9y agoOn readability, all the code samples here are using pipe operators spread out on one line, but for anything nontrivial I think the point is to use it thusly: initialValue |> doSomething |> doSomethingElse |> reportResults In which case I think it's extremely readable compared to the alternatives.
- deleted 9y ago[deleted]
- djedr 9y agoCompare: pipe(initialValue , doSomething , doSomethingElse , reportResults ) Is it really so much more readable with the operator?
- mabbo 9y agoNot that I necessarily disagree, but one counter argument would be "why even use the pipe method? You could just say square(increment(square(2))) and get the same result, right?" The point is readability. It's UX for the programmer, reducing cognitive load in understanding what's going on. Pipes are fantastic in the terminal and have never been replaced there because they are very simple glue piecing together the complex bits with a simple-to-understand model. If we can have that in JS, I'd be quite pleased even if there are other ways to do the same thing.
- djedr 9y agoThe counter argument would be wrong. The advantage of the operator and the `pipe` helper over regular function composition is that you can emphasize the flow of data through a pipeline of functions. The only advantage in readability I see here is that the operator is hard to mistake for anything else, it sticks out of the code, more than a seemingly regular function call. But this is not an advantage that justifies introducing the operator. Other than that, UX and cognitive load is the same. Pipes can indeed be fantastic! ;) * Apropos composition: why not add a composition operator while we're at it? Or we could define something like: const compose = (...args) => pipe(...args.reverse()) and then compose(square, increment, square, 2) ;) For me there is also something more ineffable that's important here. A question of whether adding a new built-in operator fits within the general philosophy or some sort of "look & feel" of the language, or whatever. I think JavaScript seems to be quite conservative, when it comes to bringing in new operators. Perhaps that's a good thing. Maybe not. But so far, I remain unconvinced.
- hajile 9y agoOne great reason is that the composing function can be optimized away much easier with the native operator.
- djedr 9y agoAnd 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?
- hajile 9y agoOne 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);
- marcosdumay 9y agoThis, and void, and promises: > 2 |> square |> increment |> square So, looks like any sufficiently complex language nowadays carries a badly self-compatible, inextensible, limited set of popular Haskell libraries. And also Lisp's seq. Other Lisp features are left for implementation on a Lisp interpreter once your application gets large.
- xixixao 9y agoYou won't be able to type `pipe` with Flow/Typescript. But they can (and had) added support for the |> operator.
- djedr 9y ago> You won't be able to type `pipe` with Flow/Typescript. Don't know what you mean by that. > But they can (and had) added support for the |> operator. Doesn't seem like they did: https://github.com/Microsoft/TypeScript/issues/17718 https://github.com/Microsoft/TypeScript/issues/17718 https://github.com/facebook/flow/issues/5443 https://github.com/facebook/flow/issues/5443
- Pxtl 9y agoImho, languages that break the abribtrary difference between functions and methods do this best, so there's no difference between Square(myNum) and myNum.Square(), since .Square() works nice for pipe-style usage.
- djedr 9y agoSure, but JavaScript is not one of those languages unfortunately.