6 ms·
I think that these bundled conditions are bad, and that having negative booleans is terrible. Using and almost abusing early returns on bad input almost always
by dietr1ch 4y ago
I think that these bundled conditions are bad, and that having negative booleans is terrible.
Using and almost abusing early returns on bad input almost always leads to easier to follow code.
if (someLongNamedVarIsNotUnknown && someLongNamedMapCountIsZero)
return false;
else
return true;
This is not the same, but I'd suspect there might be a bug in the code above
if (someLongNamedVarIsUnknown) {
return false; // Can't do much
}
if (someLongNamedMapCountIsZero) {
return false; // Nothing to do
}
// Do stuff
return true
The named booleans suggested here help adding clarity, but more often than not they'll end up being single-use (const) variables that just paraphrase their definitions. In these cases I'd rather avoid introducing a variable, and rely in comments if I really need to explain something.
- andix 4y agoTrue, functions with return values are so much better in such a case. I really miss if expressions and switch expressions in most languages. Most functional languages (and also c#) have them. Something like (let’s do JavaScript in this example): const value = if (a) { return true } else { return switch (b) { case 1: return false default: return undefined } }
- baq 4y agoInterestingly Rust does this and I agree JavaScript with const feels only 80% there without this feature.
- monocasa 4y agoAs an aside for those wondering what the name for this is to see what other languages support it, this is a case 'if' being an expression rather than a statement which allows it to be composed within other expressions.
- smitty1e 4y agoA predicate?
- tialaramex 4y agoNo, if, and match, and most things are expressions, and thus have a (typed) value. In a typical programming language we can write something like x = 5; assigning 5 to the variable x. That 5 there is an expression, we can write x = (3 * 2) - 1; with the same effect, that's a more complicated expression. We can call a function here: x = f(3); or if our language has methods we can even call those: x = some.function(parameters); In Rust I can write x = if it.should_be_nine() { 9 } else { 5 }; // because if is an expression Since Rust's loops are also expressions, x = loop { let stuff = get_stuff(); if stuff.is_good() { break stuff } }; // ... once the stuff is good, break out of the loop and we'll assign that stuff to x
- smitty1e 4y agoUnderstood, these things are expressions, but must they nit evaluate to a boolean in order for if/switch/match to branch correctly? My informal understanding of a predicate may need refining.
- tialaramex 4y agoI think the thing you're missing is that in some languages we're allowed to use these constructs as expressions. Not that they take expressions which is common in modern non-toy languages, but that they themselves are expressions. It might be you've just always used languages where you can do this, or that you assumed you can but actually you never tried in a language you use. In C for example we can't write this: int x = if (1 == 2) { 3 } else { 4 }; // Not valid C syntax C provides a special operator, the only one in the language which has three operands and thus it's often just called "the ternary operator" although other ternary operators can technically exist, to achieve the same thing: int x = (1 == 2) ? 3 : 4; // Valid C syntax using ternary operator But, how about this? int x = switch 3 { case 1: 4; case 2: 8; default: 0; }; // Can't do this either There's no way to express that in C or C++ today. The closest is the "Immediately Invoked Lambda" from C++ which is illustrated nearby in this thread. Barry Revzin apparently proposed a way to do this in C++ 26 because C++ wants to have pattern matching and pattern matching kinda sucks without this feature.
- tialaramex 4y agoApparently C++ 26 might get statement expressions in some form, it sounds like Barry Revzin sketched something at Issaquah (the recent WG21 meeting) which can do this.
- deathvyp3r 4y agoyou can do this in c++ as well with a lambda const value = [&]() { if (a) { return true; } return switch (b) { case 1: return false; default: return undefinied; } }();
- andix 4y agoSure, in a lot of languages this is possible, but usually much slower, even slower than named functions. Can the c++ compiler in-line the lambda and remove the function call completely? Still the syntax is a bit confusing. In ML languages (f# and also rust are somehow descendants of ML) you can just use if .. else if .. else expressions. They work like ternaries! But easier readable. Nested ternaries are useful, but get really hard to read very fast. (x = condition1 ? A : condition2 ? B : C;)
- monocasa 4y ago> Can the c++ compiler in-line the lambda and remove the function call completely? LLVM and GCC can at least.
- somewhereoutth 4y agoAnd indeed almost exactly like that in JavaScript - just substitute ()=> for the [&]()
- wruza 4y agoYou can’t directly call a function definition in js, so it will look like this: const value = (()=>{ … })()
- tialaramex 4y agoAn immediately invoked lambda pattern, but, check out how ugly that was compared to: let value = if a { true } else { match b { 1 => false, _ => undefined } }; And notice that because now we're a function our relationship to the world changed, how does your immediately invoked lambda do this? let value = if a { true } else { match b { 1 => false, 2 => return foo, _ => undefined }}; With expression syntax returning from the function we're in is fine† but in your lambda you can't do that, you need control flow logic outside the lambda to handle it. † You might wonder about the inferred type of this expression since it might return. The type of the return expression in Rust is ! aka Never, which is an empty type. In the same way that N + 0 == N in integer arithmetic, any type plus an empty type is the original type again in type arithmetic, so the type of the expression isn't altered.
- pyjarrett 4y agoAda calls these "if expression" and the switch-variant "case expressions". It's the same syntax as the control flow ones, just wrapped with parens. value := (if a then True else ( case b is when 1 => False, when others => Undefined ));
- monocasa 4y ago> The named booleans suggested here help adding clarity, but more often than not they'll end up being single-use (const) variables that just paraphrase their definitions. In these cases I'd rather avoid introducing a variable, and rely in comments if I really need to explain something. Interesting. With the same axioms I come to the opposite conclusion. That comments are to be avoided except when strictly necessary, and giving names to concepts via intermediate const variables is one of the best weapons in that fight. Ie. make your code read as close to what the comment would be (but simply via naming) makes it very clear when the names no longer match the behavior, in contrast to comments which have a tendency to bit rot.
- Sai_ 4y agoThis is what I used to love about Obj-C - idiomatically written Obj-C code was always so much easier to read and understand without needing comments.
- simplotek 4y ago> The named booleans suggested here help adding clarity, but more often than not they'll end up being single-use (const) variables that just paraphrase their definitions. That's ok. The worst tradeoff in the "named booleans" pattern proposed in the blog post is that it misses the whole point of chaining predicate evaluations, which is leveraging short-circuiting to prevent further predicates from being evaluated. Chaining logical comparisons and the way they short-circuit is specially important when a predicate has preconditions and/or is expensive and/or has side-effects. If your predicates are hard to read you don't fix that mess with "named booleans". You fix them by extracting them to a pure function like isNotUnknown(_someLongNamedVar) && isNotEmpty(_someLongNamedVar)
- toxik 4y agoNest the if statements then, perhaps in an else branch. Relying on short circuiting to avoid side effects or improving performance meaningfully is pretty shaky.
- BeeOnRope 4y ago> Relying on short circuiting to avoid side effects or improving performance meaningfully is pretty shaky. Says who? IME relying on short circuiting is useful and idiomatic in C and C++ code, e.g.: if (callback && callback->run()) { ... } In your opinion would it be better to write: if (callback) { if (callback->run()) { .. } } The situation is even more awkward if you try to avoid || short-circuiting, since you need to duplicate the body of the conditional.
- DubiousPusher 4y agoI tend to agree with toxic here. Side effects inside conditionals should be avoided, in short circuit conditionals even more so. In a case like this, my preference would be to give callback a dummy value and always run it. I subscribe pretty heavily to the Carmack philosophy of always running the code as similarity as possible and making a decision at the end of a procedure.
- finnh 4y agoearly return solves so many readability problems. i structure my code in favor of it.