Hacker News new | past | comments | ask | show | jobs | submit login

> every programmer worth his money just knows that ?: is right associative

I would hope every programmer worth their money are very comfortable with it, because chaining like this is a very common syntactic pattern in other people's code:

  cond1 ? val1 : cond2 ? val2 : cond3 ? val3 : other
It is made less readable if you turn it into a right-nested mess of parentheses, so that is rarely seen.

Just as nobody thinks of 'if..else' as right associative, but it actually is, and everyone uses that fact without thinking about it.

Same for '?:'. The associativity is just a formality that nobody thinks about.

> '&&' is on precedence level 11 while '?:' is on 13

On this I agree. I routinely put parentheses around complex conditions on the left side of ternaries for this reason.

I do remember the precedence, but I think leaving the parentheses out can raise a little doubt in the reader's mind, because it is common to use logical operators for control flow in languages like JavaScript, and control flow is at the same conceptual level as '?:'.




This came up recently on the Swift Evolution forum (which is the official forum for discussing changes to the Swift programming language). Dave Abrahams said, “[…] I keep meeting experienced programmers (really smart people!) that have no trouble reading [a chain of if/else statements] and yet are confused by the analogous ternary construction: [a chain of ?: expressions]”.

https://forums.swift.org/t/pitch-if-else-expressions/22366


My favourite solution to this problem is how rust does it. In rust every block can evaluate to an expression, so if/else is the ternary operator.

    let x =
      if cond1 { expr1 }
      else if cond2 && cond3 { expr2 }
      else { expr3 };
It’s more verbose this way (‘?’ Vs ‘else if’) but there’s no question of readability because it’s just if/else. You can format it however you like, and add statements into the blocks later if you need to, too.

Rust also has the match statement, which is cleaner whenever your conditions are mutually exclusive.


Very lispy :-)

Some would say top-level blocks returning the last value in the block is an anti-pattern, because functions which aren't meant to return a value end up leaking the value of the last thing called in the function, which might be another function, which called another function. Or it might be in various branches of an 'if', which aren't being examined for being an acceptable return value.

Perl does this, (like ECMAScript's 'do'), and while it's usefully concise sometimes, for API-level functions I think it's poor to accidentally leak values to the caller, that should never escape. The safe way to deal with this is an explicit void return at the end of API functions, but that's ugly and hard to remember.

I think JavaScript made the right choice in requiring explicit return from functions with blocks to return a value, with 'undefined' returned if nothing explicit is. Accidents are avoided.

Rust has taken an interesting approach of requiring a return type to be specified, which stops accidental leaks at least. Respect.


Yep! Coffeescript does it too. It was always weird seeing random values pop out of functions while debugging. Also coffeescript's loops evaluate to a list, which meant that functions which ended in a loop in coffeescript would end up constructing and returning arrays that would never be used.

Rust will also only return the last expression in a block if it doesn't end in a semicolon. This can be a bit subtle when you're reading a long function, but combined with explicitly specified return types its hard to mess up while writing code. Because of the choice about that semicolon, its an explicit choice whether you want a block to evaluate into that expression or not. And for functions you can always just use an explicit return statement if you want anyway.


Perl6 has remedied the accidental leak of values to the caller by allowing you to give a return value in the signature.

This will ignore the last result and return Nil instead:

    sub foo ( $_ --> Nil ) {…}
This only works with literals, constants and Nil.

If you specify a type instead, it only enforces that the result is of that type.


> like ecmascript's "do"

Keep in mind this is only a stage 0 proposal, and thus is not really part of the language. This is how statements behave when entered into the repl, e.g. the browser dev console.


(COND ...), anyone? :-)

Ruby has an if/else structure like the Rust/Lisp thing, as well. Tasty.


There's are two pretty simple explanations for that.

1. It's rare, so not many programmers have the pattern matching built up to read it easily.

2. Chained if/else statements have the benefit of indentations helping to show structure. The moment you add newlines to help show structure, a chain of ?:?:?:'s becomes much easier to read.

Most ?: expressions I see are uses inline, such as foo(a ? b : c). When you do that, you sometimes have to mentally unwind "ok so if a... what's a? why a? ok, so if a, then foo(b), else foo(c)". Putting the if(a) up front means you're already thinking about it by the time you get to the function call. So I reserve using ?: for when the difference between the two outcomes is minimal.


>because chaining like this is a very common syntactic pattern in other people's code

If it's very common where you work, then run, don't walk, away...


No, it's very common in code people will encounter if they read a wide range of other people's code outside work.

If you haven't encountered it often enough that it's familiar, than I think you probably don't read much code outside a small bubble.


"readability" is largely a matter of familiarity, so it's very hard to make such sweeping statements accurately. The difference between "common idiom" and "unreadable mess" is "common", not the code itself.


Strongly agree with this. My company has its own coding standards and openly acknowledges that they are not objectively "the right way to do it", because such a thing does not exist. Instead, they define rules to make our code (relatively) safe and consistent.

The Linux kernel has another set of rules that differ in numerous ways (e.g. not wrapping single-line statements under a conditional in braces). Their rules are not strictly better or worse, just different and define their set of common idioms.

Another thing that the MD said to me during my interview was "code ought to be boring, testing can be interesting".




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: