maybe it's only me, but it's really hard to reason about this.
maybe it's only me, but it's really hard to reason about this.
It should also be easier to reason about nested ternaries than an equivalent set of nested if-elses, because at least with ternaries you know that every branch is an expression resulting in some value (and statically typed languages ensure that these values have the correct type), whereas if-else blocks can contain anything, are likely (and in most languages (which lack if-else expressions), forced) to mutate things, and have no guarantee of producing the result that you were expecting, unlike ternaries (especially in statically typed languages).
Oh, but fully agreed, nested ternaries is right out. In fact, usually in those other cases too, it's just annoying that there's not a good alternative.
function deriv(exp, variable) {
if (is_number(exp)) {
return 0;
}
if (is_variable(exp)) {
if (is_same_variable(exp, variable)) {
return 1;
} else {
return 0;
}
}
if (is_sum(exp)) {
return make_sum(deriv(addend(exp), variable),
deriv(augend(exp), variable));
}
if (is_product(exp)) {
return make_product(deriv(multiplier(exp),
variable),
multiplicand(exp)))
}
return error(exp, "unknown expression type -- deriv");
}I'll add a couple of things onto this: early returns are very helpful for me in avoiding nesting if statements (although that's less applicable in this specific example).
function op(cond) {
if (cond) {
//do something
}
}
function op (cond) {
if (!cond) { return; }
//do something
}
And it's good to remember that you can basically stick functions anywhere including inline, so it's not necessarily a requirement to take a function like this and move it to a top level as a private function. If you're only using it in one place you can just define it and call it anonymously.And don't be afraid to still use ternary operators non-nested. There's a sibling comment complaining about the nested if statement. If that really bothers you, you can still do:
if (is_variable(exp)) {
return is_same_variable(exp, variable) ? 1 : 0;
} if (is_same_variable(exp, variable)) {
return 1;
} else {
return 0;
}
should be return +is_same_variable(exp, variable)"Languages do not differ in what they make possible, but in what they make easy."
It's not the biggest thing in the world, and I don't want to distract from the rest of the book, but this is a situation where writing a one or two line helper function:
const _ = (cond, a, b) => cond ? a : b;
would have made the code much more readable without much downside that I can see -- at least to my subjective opinion. Maybe I'm missing something.Edit: comment below correctly points out that if it's important for you to avoid immediate evaluation, you'll need to wrap your conditionals in functions.
_(cond, () => a, () => b)
And _ becomes: const _ = (cond, a, b) => cond? a(): b();
And it does matter in this case when looking at the last condition which signals an error (does not return an error value if I understand it correctly). In which case your _ would raise an error even when not appropriate.I'm not sure it matters here, the error you're pointing out looks to be getting returned (unless I'm misunderstanding what the book intends the `error` function to do), and creating an Error in Javascript is fine, it doesn't break your program until it's actually thrown.
Edit: just looked at your comment again, and you're saying it does actually throw the error rather than returning it :) So double-corrected on my part :)
But your point stands regardless. There will be scenarios where what you're talking about matters -- JSX also follows this pattern of immediate evaluation and yeah, I see errors from that plenty of times. So it's good to mention.
The reasoning is because ternary operations eliminate programming singularities.
Example:
var x;
if (someExpression) {
x = True;
}
//var x is undefined
Example 2: var x;
x = someExpression ? true : false;
// use of ternary expression prevents var x from ever being undefined.
The ternary expression forces you to handle the alternative case while the if expression can leave a singularity. A hole where x remains undefined.Some people say ternary expressions are less readable. But Readability depends on your "opinion." There are no hard logical facts about this. So it's a weak-ish argument.
It is actual fact (ie not an opinion) that ternary expressions are categorically more Safe then if-statements. Therefore, they are factually better in terms of hard logical metrics.
This is the reasoning behind nested ternary statements. It's also one of the strange cases in programming where superficial qualitative attributes of "readability" trumps hard and logical benefits. The overwhelming majority of the population will in fact find many nested ternary operations highly unreadable and this "opinion" overrides the logical benefit.
Usually though, to maintain safety and readability I just alias boolean statements with variable names. The complexity of a conditional expression can be reduced by modularizing parts of it under a symbolic name, you don't necessarily have to reduce complexity by forcing the conditional into the more readable bracketed spatial structures favored by if-statements.
Example:
isXgreaterThanY = x > y;
isWlessThan2 = w < 2;
isSubExpressionTrue = isXgreaterThanY & isWlessThan2 ? False : True;
isFactNotTrue = isSubExpressionTrue ? False : True;
Yes technically the ternary expressions are still nested in a way here, but when reading this code you don't have to dive in deep past the symbolic name.Imagine if you have a boolean condition that relies on multitudes of these sub expressions. By defining the final statement as a composition of Named ternary expressions you eliminate the possibility of a hole;... a singularity where one alternative wasn't considered. In my opinion this is the best way to define your logic.
If-statements, imo, should be reserved for functions that are impure (void return type).. code that mutates things and touches IO which is something you as a programmer should keep as minimal and segregated away from the rest of your pure logic as possible.
Example:
if true:
sendDataToIO(data)
//the alternative of not sending data to IO is not a singularity. It is also required... a ternary expression makes less sense here.
Last but not least: The ternary expression is also a bit weak because it only deals with two possible branches: True/False. The safest and most readable primitive to deal with multiple branches of logic is exhaustive pattern matching. Both Rust and Haskell have a form of this. In rust, it is the match keyword.In my implementation nested ternary operators were a parse error.
That said Nystrom's book has adorable drawings, and feels more "passionate".
I've read both, but I only followed the go-book because I was learning Golang at the time. No regrets.