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.
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.
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.
Rust also has the match statement, which is cleaner whenever your conditions are mutually exclusive.