As a multi paradigm language, JS typically suffers from whatever programming style is on trend when these features are implemented. We are apparently on the other side of the pendulum now, but I can’t remember the last time I worked with a class and felt like that was right either.
Pipe Operator (|>) For JavaScript
171–180 of 437 posts
Re: Pipe Operator (|>) For JavaScript
#172Temporary variables are often tedious? I have found that well named temporary variables are the only clear way to comment code without actually writing the comment. The version with temporary variables is much easier to understand without having to read the rest of the code.
This. Temporary variables are the way to go for deconstructing a complex expression like this. Everything is more readable when you put the results of an expression with two to four terms in a well-named variable. Trying to put everything into one giant closed-form expression feels clever and smart, but it's really just getting in the way of the next poor sucker who needs to understand what you were doing. This works…
Re: Pipe Operator (|>) For JavaScript
#173http://www.jaedworks.com/hypercard/HT-Masters/scripting.html
ask "How many minutes do you want to play?"
put it * 60 into timeToPlay -- convert it into seconds
Today we could have a reserved keyword that holds the result of the last statement executed. A practical example adapted from the article using "it" might look like: Object.keys(envars)
it.map(envar => `${envar}=${envars[envar]}`)
it.join(' ')
`$ ${it}`
chalk.dim(it, 'node', args.join(' '))
console.log(it);
A better name for "it" today might be "_", "result" or perhaps '$' in a shell-inspired language like php. Most shells support "$?" so that could work too: # prints 0
true ; echo $?
# prints 1
false ; echo $?Re: Pipe Operator (|>) For JavaScript
#174Temporary variables are often tedious? I have found that well named temporary variables are the only clear way to comment code without actually writing the comment. The version with temporary variables is much easier to understand without having to read the rest of the code.
When there are a few they can be really great. But if you need to accurately name every single intermediate thing they can become visual noise that hides what happens.
function bakeCake() {
return separateFromPan(coolOff(bake(pour(mix(gatherIngredients(), bowl), pan), 350, 45), 30));
}
The piped code looks like: function bakeCake() {
return gatherIngredients()
|> mix(%, bowl)
|> pour(%, pan)
|> bake(%, 350, 45)
|> coolOff(%)
|> separateFromPan(%)
;
Which is... fine? It certainly looks better than the mess we started with, but adding names here only helps clarify each step. function bakeCake() {
const ingredients = gatherIngredients();
const batter = mix(ingredients);
const batterInPan = pour(batter, pan);
const bakedCake = bake(batterInPan, 350, 45);
const cooledCake = coolOff(bakedInPan);
return separateFromPan(cooledCake);
}
Even if you consider the `const` to be visual noise, the names are useful. At any point you can understand the goal of the code on the right-hand side by looking at the name of the variable on the left-hand side. You can also visually scan the right-hand side and see the processing steps. You can also introduce new steps to the control flow at any point and understand what the data should look like both before and after your new step.I agree that the the control flow is more clearly elucidated in the pipe operator example, but it tosses away useful information about the state that the named variables contain. It also introduces two new syntactical concepts for your brain to interpret (the pipe operator and the value placeholder). I contend the cognitive load is no greater in the example with names, and the maintainability is greatly improved.
If you have an example where there are dozens of steps to the control flow with no break, I'd be really curious to see it.
Re: Pipe Operator (|>) For JavaScript
#175Earlier quoted context omitted.
It's no less legible than original code and at least expresses the intent of what's being processed, what are the processing steps and what are processing parameters. a(b(),c(),d(e(),f(g()))) is just function call soup.
At least I can instantly tell which call is ultimately returning something, in that version.
If it's really good rule you can advocate for it at this stage. It might be prudent to use |> % only inside function call parameter or possibly as right hand of an assignment instead of everywhere where parser expects an expression.
a(b(),c(), g() |> f(%) |> d(e(), %))
Although I'll be honest, I don't like it. Other parameters of a() call for me occlude the flow and the intent.
We could make it a bit better with newlines.
a(b(),c(),
g() |> f(%) |> d(e(), %))
)
But if a() takes more parameters after the main one then we have the same problem as usual where parts of the same call can end up far away from one another. a(b(),c(),
g() |> f(%) |> d(e(), %))
x(), y(), z())
For me g() |> f(%) |> d(e(), %))
|> a(b(), c(), %, x(), y(), z());
is still better and I wouldn't want it to be prevented by language syntax.Re: Pipe Operator (|>) For JavaScript
#176This makes me nervous. In general, I think adding features like this to a mature language is a misstep because it increases the cognitive load of "things you have to know to read other people's code." And that's strictly increases... Since changes like this can't remove previous approaches (for backwards compatibility reasons), we'll now have three syntaxes for function calls? Yuck. Left unchecked, this predilection…
"I like functional programming so I'm going to do that in JavaScript" -> "Now I have a problem because JavaScript is not very good at that, so now let's radically alter JavaScript until it's... well, still not good at it, but it looks like it is, at a glance" (initially through libraries, now altering the language itself)
"Let's make as many calls async by default as possible" -> "Oh but actually I need most of them to seem synchronous, even if they're actually async, like 90+% of the time" -> "Callbacks?" -> "Oh god that sucked... promises?" -> "Better but still not great, let's just... uh... add `async` and `await` and watch as their use becomes so hilariously common that it's now painfully obvious that the default behavior is wrong?"
Re: Pipe Operator (|>) For JavaScript
#177Is this: Object.keys(envars) .map(envar => `${envar}=${envars[envar]}`) .join(' ') |> `$ ${%}` |> chalk.dim(%, 'node', args.join(' ')) |> console.log(%); Really better than: console.log(chalk.dim( `$ ${Object.keys(envars) .map(envar => `${envar}=${envars[envar]}`) .join(' ') }`, 'node', args.join(' ') )); That's the real-world example they have (I reformatted the second one slightly, because it looks better to me). N…
A more aggressive reformulation would be to prefix the original code with
const envOutput = Object.keys(envars).map(envar => `${envar}=${envars[envar]}`).join(' ');
const argsOutput = args.join(' ');
Leaving the example being converted as simply: console.log(chalk.dim(`$ ${envOutput}`, 'node', argsOutput));
vs envOutput |> `$ ${%}` |> chalk.dim(%, 'node', argsOutput) |> console.log(%);
This more clearly highlights the obvious limitations of pipelines - piping envOutput but not 'node' nor argsOutput is a jarring syntax-mix here. Though I think it offers some hope for them working well in other scenarios - possibly in curry-heavy applications.Re: Pipe Operator (|>) For JavaScript
#178This strikes me as something better left to libraries. If you want to write in a functional style then Ramda, Lodash, Underscore, and plenty of others have pipe and compose functions. pipe(one, two, three) Easy to read. No new syntax. Extendable with arrow functions. Yes, there are some limitations in comparison to Hack Pipes. But those are far outweighed by not messing yet again with the language’s syntax.
In order for TypeScript to check if the return value of one function has the correct type for the next function, you need to use generics, but generics don't allow for a variable number of generic parameters, so `pipe` would just have to use unknown or have dozens of overloads for each length of pipe usage (within reason).
I know TypeScript is a different, optional language, and I see no reason why they couldn't add the feature without it being in JavaScript, but that's not generally how TypeScript operates. If JavaScript doesn't add it, TypeScript won't. There's also lots of tooling that will type check your JavaScript when available, which will benefit from a simpler model.
Re: Pipe Operator (|>) For JavaScript
#179Temporary variables are often tedious? I have found that well named temporary variables are the only clear way to comment code without actually writing the comment. The version with temporary variables is much easier to understand without having to read the rest of the code.
Re: Pipe Operator (|>) For JavaScript
#180Earlier quoted context omitted.
Nested calls can be a code smell, sure. But easily fixable: one(two(three())); Becomes let a=three(); let b=two(a); one(b); Clean and easy, no sugar required.
Lots of low-value names required. Those names will inevitably either be badly chosen or have taken far more time to make up than they are worth. That's assuming the code isn't written by that one guy on every team who stubbornly insists on var x = three(); x = two(x); one(x): (having the names certainly is nice to have in the debugger, but I'd rather have those intermediate results be an explicit debugger feature tha…