Zig: Remove the requirement of () for if, while, for, switch, etc.

Created on 16 Oct 2018  路  17Comments  路  Source: ziglang/zig

With #1628 solving #760, the parens around expressions in if, while, for, switch, etc. should not be necessary anymore.

proposal

Most helpful comment

Without requiring parentheses around the condition of if, it's impossible to put a negative number in the "then" expressions:

this:      return if a - b else c;
parses as: return if (a - b) else c;

and this:  return if (a) -b else c;
parses as: return if ((a) - b) else c;

I think this issue is a bad idea. We need a way to unambiguously signal the end of the if condition. Currently it's a ) token (and the opening ( is only there to look balanced), or it could be then like in Lua. I prefer parentheses, but we can't have nothing.

All 17 comments

What about if expressions returning values?

x = if (c) a else b;

@andrewrk Good point.

x = if if true true else false 1 + 2 else 3; 

I personally don't mind the parens on if, while, etc, but I wanted to preserve this proposal after closing #760.

This looks confusing to me no matter if parens/braces are omitted or not because there are so many levels of branching on a single line.

x = if if true true else false 1 + 2 else 3;
x = if (if (true) true else false) 1 + 2 else 3; // better? barely
x = if if true { true } else { false } { 1 + 2 } else { 3 }; // still hairy
x = if (if (true) { true } else { false }) { 1 + 2 } else { 3 }; // better but still a lot at once

Personally I think it's a good thing to enforce fewer parens on the user. Whether the code becomes more or less readable comes down to what the reader is used to. In the end it comes down to personal preferences again.

However, what about non-human readers? Do the parens help the compiler to emit better error messages? Is it easier for tools to parse and understand the code with enforced braces? I imagine dropping parens here will make it harder for text editors to trivially detect the code structure (I'm thinking about sublime text 3's way of defining syntax via regex, not sure how other editors do it).

Status quo

if (a) { stuff; } else { other; }
x = if (c) a else b;
x = if (if (a) b else c) d else e;

while (a) { stuff; }
while (maybe_a) | a | : maybe_a = a.next { stuff; }
var node = blk: while (maybe_a) | a | : maybe_a = a.next {
    if (a.x == 0) break :blk a;
} else null;

for (all_your_base) | r | r.belongToUs();

switch (a) { 0 => { stuff(); } }
var x = switch (a) { 0..32 => 32; else => a; }

Alternative - Syntax more consistent with switch

switch a { 0 => { stuff(); } }
var x = switch a { 0..32 => 32, else => a };

if a => { stuff; }, else => { other; }
x = if c => a, else => b;
x = if if a => b, else => c => 1 + 2, else => 3;

while a => { stuff; }, else => { other; }
while maybe_a | a | : maybe_a = a.next => { stuff; }
var node = blk: while maybe_a | a | : maybe_a = a.next => {
    if a.x == 0 => break :blk a;
}, else => null;

for all_your_base | r | => { r.belongToUs(); }

// or even

if { a => { stuff; }, else => {other}};
var x = if {c => a, else => b};
var x = if { if { a => b, else => c} => 1+2, else => 3 };

while { a => { stuff; }, else => { other; } }
while { maybe_a | a | : maybe_a = a.next  => { stuff; } }
var node = blk: while { maybe_a | a | : maybe_a = a.next => {
    if a.x == 0 => break :blk a;
}, else => null };

Cons:

  • Many =s will make the line noisy, probably looks uglier than many parentheses.
  • Not a familiar syntax.
  • Sometimes we end up requiring lots of {}. We could allow some omission even in switches though
switch a 0..32 => { stuff; }, else { other; }
var s = switch a 0..32 => 32, else => a;

Some languages when construct that acts like a chain of if/elseifs:

when {
    a == b => { doStuff; },
    a == b * 2 => { otherStuff; }
}

var c = when {
    a == b => 2 * a,
    a == b * 2 => 3 * a,
    else => 0
}

This goes hand-in-hand with something to do with the return type ambiguity issue. I've mentioned before but never got around to a proposal: Function expressions!

fn double(num: f32) f32 => num*2;

// to resolve the ambiguity, we'd always need the =>
fn rational(numerator: i32, denominator: i32 ) error{}!i32 => {
    return if b == 0 => error.DivByZero else => a / b;
}

this is ambiguous:

const b: bool = if a - b == c -d -c == -d else - a - b;
const b: bool = if (a - b == c -d) -c == -d else - a - b;

I think if and its statement/block and else should not be allowed on the same line at all, then removing the parenthesis is natural. There is no reason to allow that kind of shorthand, it never really "helps". It's one of those features where people feel smart because they can cram a lot of logic into a single line but does it help? Does it really help when you read it? No of course not, it just looks a tiny bit neater when you first wrote it and then it's worse every single time you have to read it.

@Meai1 I do agree with your rationale and premise however I don't think we can do that here since that would have the effect of making zig's grammar whitespace sensitive which I don't think we want.

Not a 100% sold on this syntax, but it is optional so all ambiguous code is solved with parens. Its really an infix issue, so lets just switch to postfix!

if a b - c d - ==  -c -d == else  -a -b == 

And yes the ambiguous code even more noticeable in the postfix notation.

allow the usage of then for expressions
force the usage of () for embedded if/then/else

x = if c then a else b;
x = if (if a then b else c) then d else e;

for statements, well, {} are clear enough

usage of {} should remain consistent across for/while/if

if a {stuff;} else {other_stuff;}
while a {stuff;}
for all_your_base |r| {r.belongToUs();}

no idea for a proper switch syntax :(

Without requiring parentheses around the condition of if, it's impossible to put a negative number in the "then" expressions:

this:      return if a - b else c;
parses as: return if (a - b) else c;

and this:  return if (a) -b else c;
parses as: return if ((a) - b) else c;

I think this issue is a bad idea. We need a way to unambiguously signal the end of the if condition. Currently it's a ) token (and the opening ( is only there to look balanced), or it could be then like in Lua. I prefer parentheses, but we can't have nothing.

Without requiring parentheses around the condition of if, it's impossible to put a negative number in the "then" expressions:

this:      return if a - b else c;
parses as: return if (a - b) else c;

and this:  return if (a) -b else c;
parses as: return if ((a) - b) else c;

I think this issue is a bad idea. We need a way to unambiguously signal the end of the if condition. Currently it's a ) token (and the opening ( is only there to look balanced), or it could be then like in Lua. I prefer parentheses, but we can't have nothing.

yes we can, if we remove the unnecessary ability to put multiple statements on the same line with an if

@Meai1 I do agree with your rationale and premise however I don't think we can do that here since that would have the effect of making zig's grammar whitespace sensitive which I don't think we want.

well I cant even type tabs without the zig compiler throwing errors, I'm sure people dont mind a sensible restriction that prevents you from stuffing several nested if conditions into a single line. Golang has that too, it doesnt allow an if without {} braces.
I think most people would already consider such a usecase harmful anyway, surely Google and other big companies have coding styles that forbid if/while/for loops that omit braces or putting multiple statements into a single line.

wait, is it only prefix symbols that are causing this issue?

x = if if true true else false 1 + 2 else 3; 

This isn't ambiguous, its bad code. You never nest turnary statements, so why would you nest an if statement in the conditional? If we want to avoid this foot gun then we should not allow an if statement in a conditional.

We could even require that nested ifs must be in { } blocks.

Remember that blocks in { braces } cannot return a non-void value without a label. So we can't usefully use x = if (a) {b;} else {c;}; without labels like x = if (a) blk: {break :blk b;} else blk: {break :blk c;}; or x = blk: {if (a) {break :blk b;} else { break :blk c;}};. Those are way worse than just x = if (a) b else c; which is status quo. Requiring braces on turnary expressions is untenable.

Go abolished ternary operators altogether(link) because "the language's designers had seen the operation used too often to create impenetrably complex expressions.". I have not suffered from this, but it is an interesting argument. I'd like to see how that rule plays out in real actual Go code that would otherwise use ternary operators.

wait, is it only prefix symbols that are causing this issue?

The ambiguity comes from tokens that can either be between two primary expressions or come before a primary expression. - is the classic example of this, and we're definitely not going to change that operator. Other examples are (, [, *, &.

Regarding blocks with braces, there's still #732 for the new result keyword.

if a { stuff; } else { other; }
x = if c { result a; }  else { result b; };
x = if if a { result b; } else { result c; } {result 1 + 2; } else { result 3; };

while a { stuff; } else { other; }
while maybe_a | a | : maybe_a = a.next { stuff; }
var node = while maybe_a | a | : maybe_a = a.next {
    if a.x == 0 { result a }; // not so sure if the destination of this result is completely unambiguous
} else { result null; };

That's not so bad.
Or with then, for completeness

if  a then { stuff; } else { other; };
var x = if a then b else c;
var x = if if a then b else c then 1+2 else 3;

while a then { stuff; } else { other; };
while maybe_a | a | : maybe_a = a.next then { stuff; }
var node = blk: while maybe_a | a | : maybe_a = a.next then {
    if a.x == 0 then break :blk a;
} else null ;
Was this page helpful?
0 / 5 - 0 ratings