Skip to content

Remove the requirement of () for if, while, for, switch, etc. #1659

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Hejsil opened this issue Oct 16, 2018 · 17 comments
Closed

Remove the requirement of () for if, while, for, switch, etc. #1659

Hejsil opened this issue Oct 16, 2018 · 17 comments
Labels
proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Milestone

Comments

@Hejsil
Copy link
Contributor

Hejsil commented Oct 16, 2018

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

@andrewrk
Copy link
Member

What about if expressions returning values?

x = if (c) a else b;

@Hejsil
Copy link
Contributor Author

Hejsil commented Oct 16, 2018

@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.

@andrewrk andrewrk added the proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. label Oct 16, 2018
@andrewrk andrewrk added this to the 0.4.0 milestone Oct 16, 2018
@Manuzor
Copy link
Contributor

Manuzor commented Oct 16, 2018

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).

@raulgrell
Copy link
Contributor

raulgrell commented Oct 16, 2018

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;
}

@thejoshwolfe
Copy link
Contributor

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;

@Meai1
Copy link

Meai1 commented Oct 17, 2018

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.

@tiehuis
Copy link
Member

tiehuis commented Oct 17, 2018

@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.

@bheads
Copy link

bheads commented Oct 17, 2018

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 == 

@bheads
Copy link

bheads commented Oct 17, 2018

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

@gpicquot
Copy link

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

@gpicquot
Copy link

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 :(

@thejoshwolfe
Copy link
Contributor

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.

@Meai1
Copy link

Meai1 commented Oct 23, 2018

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.

@bheads
Copy link

bheads commented Oct 23, 2018

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

@bheads
Copy link

bheads commented Oct 23, 2018

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.

@thejoshwolfe
Copy link
Contributor

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 (, [, *, &.

@raulgrell
Copy link
Contributor

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 ;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Projects
None yet
Development

No branches or pull requests

9 participants