• Prime Mover (unregistered)

    Ternaries, bah.

    Write it long form to start with, then when you know it works (which is when you've done all your unit tests - you have done your unit tests, haven't you?), you can start thinking ab out whether some of the if-else-endif patterns would benefit from being streamlined into a ternary.

    Then test it again.

    One is reminded of a meme that goes something like:

    "Hey you there: Test it. And when you get there, test it from there too. Then test it some more. Keep testing it until you get back here. Then test it again."

    except that instead of "test it" it says something rude meaning "go away". I'm sure you know which one I mean.

  • Allie C (unregistered)

    I loved learning how to write ternaries, but I never use them. They might look cool or more efficient, but they present more obfuscation for someone learning the system. Better to write long-form and make it clear!

  • (nodebb)

    I actually like using ternaries. But you know what a ternary's best friend is? Parentheses.

  • (nodebb)

    I can't figure out any operator precedence rules that end up with that line making sense. No matter what I come up with, it always results in a boolean operation on an integer.

  • (nodebb) in reply to Allie C

    Honestly, I don't consider ternaries themselves a big issue. Unlike an if/elif/else chain, they guarantee, that a value is assigned, which help readability in my book.

    Let's see...

    count = 
        var.internal == true || var.provision == true ? 
            length(var.listener) :
        0 && var.internal == false || var.provision == true ?
            length(var.listener) : 
        0
    

    With ternaries, a little code formatting goes a long way.

    The issues do however start when using ternaries for relatively complex conditions. Also, I'm not aware of any editors, that have good support for formatting ternary expressions, or even seeing good conventions.

    Heck, I still can't find anything better than

    if (
            some_long_condition_1() and
            # ...
            some_long_condition_2()
    ):
        actual_body()
    

    for Python.

  • (nodebb) in reply to Dragnslcr

    I just checked the Terraform docs and the Ternary operator is called a "conditional expression" and has a separate section to all the operators. So I assume that it has lower precedence than any operator. This means it looks something like this:

    count    = 
        var.internal == true || var.provision == true 
            ? length(var.listener) 
            : 0 && var.internal == false || var.provision == true 
                ? length(var.listener) 
                : 0
    

    So count will either be assigned length(var.listener) or 0 but unless 0 automatically casts to false, there is an error in 0 && var.internal == false || var.provision == true. Perhaps it doesn't matter because short circuiting probably means the conditional never gets executed because true || anytihng is true

    Addendum 2022-03-30 08:27: That last bit is bollocks, sorry. Confused about == true || and = true ||

  • John Melville (unregistered)

    Ternaries in my mind are good when they clearly express the intent. If the intent is to choose between two values based on a boolean then a ternary is the way to go. If/Else dictates control flow, and using control flow just to pick a value is less expressive of the intent.

    Nested ternaries get ugly, but nested any operators get ugly. We have the good fortune that addition, multiplication, "and", and "or" are all associative and communitive, which obscures some of the ugliness, but determining the actual grouping and order of operations depends on subtle and rarely read sections of the language spec.

    The solution to the nested ternary problem is the same as the solution to the nested any expression problem. Extract the subexpressions into their own method, or their own variable if you have to, and give that thing a clear, expressive name. No nested ternaries, no confusion about grouping-- and any decent compiler will inline it anyway if it helps you.

    In my many decades of programming I have found that the "never use this confusing language feature" crowd to generally be less informed. (With one exception -- goto really is considered harmful!) Everything from ternaries, to pre/postincrements, to exceptions, to more than one exit per function has been dubbed by someone as too confusing for ordinary mortals. I say phoey on all of them. Every language feature has a purpose, and every language feature can be misused to make code unreadable. Use the language, and all the language, to express what you mean. Then break it up into small enough named functions and variables that that meaning is obvious to someone fluent in the language you are using.

  • (nodebb) in reply to John Melville

    Even goto is apparently not as universally bad, as I used to think.

    I have recently learned, that it is a best-practice of sorts for “found error code, go to cleanup section of the function” in C.

    Prior to this, I had somewhere picked up the pattern

    do {
        /* ... more ... */
        errorcode = do_action();
        if(errorcode != 0) break;
        /* ... more ... */
    } while(0);
    /* cleanup resources */
    

    and was subsequently recommended, that this constitutes one of the few cases, where goto is the right choice, e.g.

    /* ... more ... */
    errorcode = do_action();
    if(errorcode != 0) goto cleanup;
    /* ... more ... */
    cleanup:
    /* ... */
    return errorcode;
    

    Also an argument for the “single point of return” rule...

    Addendum 2022-03-30 08:47: By the way, the style-sheet does funny things with <pre/> blocks.

  • eth0 (unregistered)

    You gotta love how the Hashicorp guys started out with the noble intent of creating a 100% pure declarative IaC language and quickly realized the unmaintainable, copypasty mess they would create, so they started adding all sorts of hacky features to it to end up with a slightly more dynamic but even more unmaintainable mess.

  • (nodebb) in reply to Medinoc

    I actually like using ternaries. But you know what a ternary's best friend is? Parentheses.

    But... But... But those need the SHIFT key!

  • Barry Margolin (github)

    Say what you like about PHP, but the PHP 8.0 designers made a good decision: Nested ternaries are required to use parentheses.

  • Tim (unregistered)

    It looks to me as if the person who wrote the expression thought that the ternary operator had a higher precedence than "&&" and "||" - I think it was intended as an and/or expression with 2 of the sub-expressions being ternaries

    Ternaries (and even binary infix operators) suck whenever the expressions are non-trivial. The best solution by far is lisp/clojure - no operator precedence and all your nesting and layout problems are trivially solved no matter how complex the expressions and sub-expressions are

  • Kleyguerth (github) in reply to John Melville

    There is one more example of a really bad language feature: javascript's "with". It's use is even forbidden in "strict mode" and the ECMAScript Language Specification explicitly discourages its use.

  • Carl Witthoft (google)

    Am I the only misbehaving coder who occasionally writes

          
    x = TRUE      
    if(something) x = FALSE    
    
  • I dunno LOL ¯\(°_o)/¯ (unregistered) in reply to Bananafish

    But... But... But those need the SHIFT key!

    So do the ":" the "?" the "&" and the "|", so I don't see the problem here.

  • (nodebb)

    As Neil Degrasse Tyson said, before trying to terraform, much easier to fix our own place.

  • (nodebb) in reply to Carl Witthoft

    Am I the only misbehaving coder who occasionally writes x = TRUE; if(something) x = FALSE

    No, probably not. Though I've mostly used it in Fortran to make optional-argument initialization a one-liner.

    index = 0; if(present(o_index)) index = o_index
    

    I've also run into situations where the scoping rules of the language forbid

    if(CONDITION) {
        const x = THEN_VALUE;
    } else {
        const x = ELSE_VALUE;
    }
    // x undefined here
    

    Addendum 2022-03-30 12:43: Point of the latter being: At this point it is attractive to do

    let x = ELSE_VALUE;
    if(CONDITION) x = THEN_VALUE;
    

    though it would still be clearer, if more verbose, to do

    let x;
    if(CONDITION)
        x = THEN_VALUE;
    else
        x = ELSE_VALUE;
    
  • MaxiTB (unregistered)

    I'm confused, I don't know a language where "&&" and "||" have a higher precedence than most operators, while "?:" generally has the highest of all operators.

    So this code is basically:

    count =
    ((var.internal == true || var.provision == true) ? (length(var.listener)) : (0)) && ((var.internal == false || var.provision == true) ? (length(var.listener)) : (0))

    I find languages these days odd that allow to mix up booleans with integers but apparently this is fine in this language.

  • MaxiTB (unregistered) in reply to Tim

    I beg to differ, it's all about formatting your code in a proper way. if statements can be super verbose compared to the compact better readable ?: operator - as long as you don't write spaghetti code, especially with lambdas.

  • (nodebb) in reply to R3D3

    Honestly, I don't consider ternaries themselves a big issue. Unlike an if/elif/else chain, they guarantee, that a value is assigned, which help readability in my book.

    I'm going to sit here and laugh at you for believing a total myth.

    Ternaries do not guarantee that. Seen in an actual codebase:

       (x < 5) ? a = b + c : 0;
    

    What does that assign if x >= 5? (In reality it was an attempt to avoid writing braces round an inner if() that didn't need an else.

    I've also seen:

        ((x < 5) ? func1 : func2)(some,args,here);
    

    Where func1 and func2 were actual function names, not pointers-to-functions.

  • Pecked Hen (unregistered) in reply to Steve_The_Cynic

    I actually like the ternary function choice!

  • Prime Mover (unregistered) in reply to Carl Witthoft

    "Am I the only misbehaving coder who occasionally writes

    [code]
    x = TRUE
    if(something) x = FALSE [code] ?"

    Absolutely not at all. I use it all the time. Sometimes it just makes more sense to do it that way.

    You can't explain under what circumstances it does make sense so to do, it just feels right.

    More and more I've programming by aesthetics nowadays. I can't begin to explain those aesthetics.

    I even have a good case for the use of GOTO. There is a time and a place. Knowing where and when is again something I can't easily explain.

  • MaxiTB (unregistered) in reply to Steve_The_Cynic

    To be fair, a lot languages do not have return values for assignments; I remember C/C++ have them but for example C# does not, so your example would result in a compiler error.

    Another horror construct back in the C/C++ days was a=b=c++ or worse a=b,c++

  • JB (unregistered) in reply to Steve_The_Cynic

    You've misinterpreted R3D3's comment. A ternary operator expression always produces a value, so if you use one on the right side of an assignment it is guaranteed that the variable will always get some value. On the other hand, if/else is usually a statement and so does not produce any value. Now, if you choose to not use the result of the ternary expression it just means the returned value is thrown away.

    With respect to your examples,

    (x < 5) ? a = b + c : 0;

    produces the value 0 when x >= 5. The ridiculous use of an assignment in the ternary expression doesn't change what R3D3 said. If this expression were on the right side of an equals sign, the variable on the left side would be assigned the value 0. Now,

    ((x < 5) ? func1 : func2)(some,args,here);
    

    is a creative way to switch the function to be called, but still produces either func1(some, args, here) or func2(some, args, here). Again, you can choose to use the value or not, but it is always there. By the way, if you're writing in C, function names implicitly get converted to pointers to those functions when used in expressions or argument lists, so it doesn't matter whether you use f or &f in a context like this.

  • decayops (unregistered)

    I've been working with Terraform a lot recently. Some things to note:

    There is no if statements in Terraform/HCL. The way to do this is is with this ternary. "count" is reserved keyword used to determine the number of resources of that type to create. It must be a number and greater or equal to 0. Booleans in Terraform get automatically converted between boolean and strings but I don't see anything about converting booleans to integers.

    For variables of type boolean, they can be set to true, false, or everyone's favorite, null. The || and && operators will error out if passed a null.

    I ran some tests using Terraform console in v1.1.7 and here is the output for various input values:

    If var.internal is true and var.provision is true, count will get assigned the value of length(var.listener) If var.internal is true and var.provision is false, count will get assigned the value of length(var.listener) If var.internal is false and var.provision is true, count will get assigned the value of length(var.listener) If var.internal is false and var.provision is false, I get an "Error: Invalid operand, Unsuitable value for left operand: bool required." This looks to be because it is trying to use the zero as an operand to the '&&'.

  • (nodebb) in reply to R3D3

    I actually just suggested to a coworker that they consider using if (x) { y = z; } else { y = null } rather than y = null; if (x) { y = z;} for clarity. But it was definitely more something to think about than a definite rule. (In this particular case there was more space between the two assignments than there is here, making it easy to miss the fact that they're essentially alternate code paths.)

  • Loren Pechtel (unregistered)

    I don't mind complex ternaries. Don't even think of doing a non-trivial ternary without formatting the text to show the actual structure. To do otherwise is to ask for a problem like this.

  • Ternary Apologist (unregistered) in reply to konnichimade

    A ternary would be clearer than either of those! y = (x) ? z : null;

    Ternaries are awesome, complex boolean expressions are TRWTF.

  • WTFGuy (unregistered)

    @John Melville ref

    In my many decades of programming I have found that the "never use this confusing language feature" crowd to generally be less informed. (With one exception -- goto really is considered harmful!) Everything from ternaries, to pre/postincrements, to exceptions, to more than one exit per function has been dubbed by someone as too confusing for ordinary mortals. I say phoey on all of them. Every language feature has a purpose, and every language feature can be misused to make code unreadable. Use the language, and all the language, to express what you mean. Then break it up into small enough named functions and variables that that meaning is obvious to someone fluent in the language you are using.

    Overall I agree with your thinking. Well said. But ...

    There is an issue in your very last quoted sentence. You've made an implicit assumption the other employees working your codebase are fluent in the language. In my decades of experience, that's the assumption most often falsified by the reality around me.

  • (nodebb) in reply to WTFGuy

    There is an issue in your very last quoted sentence. You've made an implicit assumption the other employees working your codebase are fluent in the language. In my decades of experience, that's the assumption most often falsified by the reality around me.

    Too relatable. Our code base is full of misunderstood language features, and unnecessary boilerplate dating back to older language versions, but still being added in new code.

  • (nodebb) in reply to R3D3

    Too relatable. Our code base is full of misunderstood language features, and unnecessary boilerplate dating back to older language versions, but still being added in new code.

    Also, I keep reading stories along the lines of "I was assigned this internal code for maintenance, and I have never touched that language before."

  • (nodebb) in reply to I dunno LOL ¯\(°_o)/¯

    So do the ":" the "?" the "&" and the "|", so I don't see the problem here.

    Yes, they do. But they're so far up on the keyboard..... Point being, there's always another excuse for not doing the right thing or, at least, something "sensible".

  • Gnasher729 (unregistered)

    One case where you need “x == true” is in Swift if you use optional bool: An optional bool can be true, false or nil. You can’t use an optional bool in an if statement. b = true means true, not nil, not false. b != false means not false, but true or nil. b == nil means nil, not true to false. b != nil means true or false, not nil.

  • Gnasher729 (unregistered) in reply to Tim

    I don’t actually care about the precedence of ?: vs && or ||. I assume that the reader, the writer or both got it wrong so you need parentheses.

  • (nodebb) in reply to Tim

    t looks to me as if the person who wrote the expression thought that the ternary operator had a higher precedence than "&&" and "||" - I think it was intended as an and/or expression with 2 of the sub-expressions being ternaries

    I think so too. Of course that just means there are two more anti-patterns on display here (beyond bool==true and bool==false comparisons).

    (bool || cond1) && (!bool || cond2) is just a complicated way of writing bool ? cond2 : cond1

    And then you get the fact that cond1 and cond2 are the same, so under this interpretation the whole thing just collapses to the inner ternary:

    count = var.provision ? length(var.listener) : 0.

Leave a comment on “The Load Balancer Got Terned Off”

Log In or post as a guest

Replying to comment #:

« Return to Article