A pipe operator for JavaScript: introduction and use cases
2ality.com
2ality.com
This is the same with the pipe operator, we already have arrow functions, thenables, and TWO different types of .pipe(), which makes syntax a lot cleaner when needed. IMHO adding this pipe operator "|>" would be a mistake, it only complicates the language making:
- Browser competition harder, when the language is really broad and difficult it makes it harder to create a new engine.
- Mastering Javascript harder, there's a lot broader amount of concepts there which are not really useful except.
- No real benefit as I explained. For some people's worldview it's better, but for us the rest of mortals[1] it's just overhead.
A really good indicator of whether something is useful in core JS is: has someone made it into a library or sublanguage[2] that is widely used? Or a language feature? Promises and events def passes that test, while Symbols, classes, generators and this new pipe operator definitely don't. Bigint is arguable, since it passes the test but remains fairly fringe use-case in JS.
[1] Note that I'm the first one to experiment with JS and try weird things! I'm just saying that these should remain broken experiments on my computer, or nice libraries in npm, but not part of the specification that no one uses.
[2] Coffescript, JSX, Typescript, etc.
1) Advanced features that cannot be replicated without runtime support
2) Syntax sugar
Symbols, BigInt, and generators are the former. Pipes and classes are the latter. (1) gives library authors access to low-level primitives that can allow them to do things that were literally impossible before (I've gotten a lot of use out of Symbols, personally), but they aren't often used directly in application code. (2) are usually more application-facing, and may or may not be a big enough quality of life improvement to justify their existence.
I guess what I'm saying is: just because you haven't seen lots of Symbols or generators in the wild doesn't mean they aren't having an impact, or weren't worth adding.
It’s probably getting to the point where it’s no longer necessary in a lot of cases, but these constructs that “nobody uses” were able to implement the newer and more widely features long before they were available in browsers.
(1) Adds more possibilities, performance improvements, operational shortcuts.
(2) Adds redundant way to do one thing. IOften confuse more than help in the long run.
The former also should not be excluded from application code. Application coder should also be familiar with what those are for.
> they aren't often used directly in application code
The problem for me with this is that abstractions leak, and suddenly you are wondering why you cannot add two simple numbers or why there's a 3rd level of hidden properties in objects.
Regarding classes, intuitively I agree, but I'm curious about your reasoning.
Also, JavaScript has some rather quirky behavior. I'd rather see a stricter JavaScript without the quirky stuff take it's place.
FYI, symbols are incredibly useful!
My feelings about the "Hack pipe operator" are much more complicated. I can see how it dramatically expands the usecases for piping as a whole, given JavaScript's other norms and constraints (no automatic currying, lots of method calls). But I also think it tangles up control-flow in weird ways that could get really hairy fast. And at some point, when you're no longer "just piping" into a unary function, are you really gaining much benefit over the normal function call syntax?
If I had to pick, I'd choose the more limited F# style, it's easier to understand and imposes some sane constraints around usage.
No more JS code golf, brevity in itself should be a non-goal
[1] https://dvt.name/2018/06/02/spread-syntax-breaks-javascript/ (excuse the code formatting, one of my plugins broke)
Really though it will come as a combination proposals - "interface types" being one of them
A = 'hello world'
console.log( A ) // prints 'hello world'
console.log( (A) ) // also prints 'hello world'
But this breaks with a random-ass error because of how arrow functions interact with `...rest` parameters A = ['hello', 'world']
console.log( ...A ) // prints 'hello world'
console.log( (...A) ) // SyntaxError: expected '=>' after argument list, got ')'
Is still absolutely hilarious and reeks of "design by committee."Engines might be able to improve the error message, but I can’t say I’ve ever run into that one.
const dotdotdot = <A>(f: Function) => (list: Array<A>) => f.apply(this, list)
dotdotdot(console.log)([10, 20, 30])
console.log(...[10, 20, 30])> const y = h(g(f(x)))
This example of nested function calls needs to be read from the inside out (execution is f, g, then h) which is unintuitive to how we read and parse text.
The alternative is to use intermediate variables instead of nested function calls to represent the control flow of f, g, h.
Debugging becomes a lot easier this way with a break point available at each step of the process.
There's nearly no disadvantage to creating the extra variables given how quickly they can be cleaned up by the GC in these cases.
The intermediate variables still have value, just not as much as they used to, and they do (arguably) increase the cognitive load for the reader
That's one alternative.
Another alternative is to use a function to apply functions in readable order (which effectively creates a context where “,” is the pipe operator.)
const pipe = (x, ...fns) => funs.reduce((acc, fn) => fn(acc), x);I agree that pipe is more readable.
I didn't say it was a better alternative, just one that is available.
> Besides that how many times do you need to call three functions without additional parameters?
That...depends on programming style, quite a lot.
Admittedly I don't have a stellar solution to this though. Only alternative I can think of is ~
[0]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
In repos you're contributing to.
Checkout this list of new JS/Node.js features [0] as an example.
Will a world with the following be an improvement? [1]
- ??=
- ||=
- &&=
And don't forget the strife around the Python "walrus operator" := [2]
Or is Scala's way what languages eventually converge into?
Funnily, in Scala, all of these are just simple plain methods. Including the pipe "operator". So Scala as a language is actually simpler here.
TC39, as the article notes, and where the F#-style pipe has been shot down enough that it's been abandoned in favor of the Hack-style one by those championing a pipe operator.
const testPlus = () => {
assert.equal(3+4, 7);
} |> Object.assign(%, {
name: 'Test the plus operator',
});
The previous code is equivalent to: const testPlus = () => {
assert.equal(3+4, 7);
}
Object.assign(testPlus, {
name: 'Testing +',
});
We could also have used the pipe operator like this: const testPlus = () => {
assert.equal(3+4, 7);
}
|> (%.name = 'Test the plus operator', %)
;
I don't really see the point in general purpose chaining. I don't really see the point of flatmapping tons of nested function calls. Instead we can abstract it and start another nested stack if necessary. I thought we moved away from utilizing promises towards async await because of the chaining issue. It feels like it's being repeated.The example from TC39 is also fairly insipid:
console.log(
chalk.dim(
`$ ${Object.keys(envars)
.map(envar =>
`${envar}=${envars[envar]}`)
.join(' ')
}`,
'node',
args.join(' ')));
I don't think it should have been written like that. The Object.keys().map().join() should've been assigned to an appropriately named variable before being used in another function. There are better ways to avoid writing code this way.typeof null === "object" language consstency etc
but hey, let's add more syntax sugar