zig fmt: ternary if inside switch could be improved

Created on 2 Jul 2020  路  8Comments  路  Source: ziglang/zig

pub fn main() void {
    return switch (foo) {
        0 => if (xyz)
            abc
        else
            def,
        else => ghi,
    };
}

To me, the if (foo) ending up being to the right of the "then" expression seems awkward, and even more so to have the else keyword further left again so that it lines up with the switch cases, which I find confusing.

Something that I'd find more natural would be along the lines of

pub fn main() void {
    return switch (foo) {
        0 =>
            if (xyz)
                abc
            else
                def,
        else => ghi,
    };
}

Where the if is moved to a new line since it's a multi-line construction and indented along with its else.

proposal

Most helpful comment

My ideal here would be

const variable = if (xyz) abc
    else def;

If blocks are used we could keep the status quo:

const variable = if (xyz) blk: {
    break :blk abc;
} else blk: {
    break :blk def;
};

For the example from the OP this would yield:

pub fn main() void {
    return switch (foo) {
        0 => if (xyz) abc
            else def,
        else => ghi,
    };
}

All 8 comments

I'm undecided, but want to point out it's not just inside switches, zig fmt also does this:

    const variable = if (xyz)
        abc
    else
        def;

My ideal here would be

const variable = if (xyz) abc
    else def;

If blocks are used we could keep the status quo:

const variable = if (xyz) blk: {
    break :blk abc;
} else blk: {
    break :blk def;
};

For the example from the OP this would yield:

pub fn main() void {
    return switch (foo) {
        0 => if (xyz) abc
            else def,
        else => ghi,
    };
}

@ifreund That seems fine, my main issue is how confusing having the else from the if line up with the switch cases was when expressions are larger and more nested. In your first example the test and the "then" expression are both short, how would the "then" expression be indented if they become too large to fit on a line? Possibly I'm doing it wrong and these expressions should be kept shorter with functions that should inline but zig fmt will need to handle it one way or another.

pub fn main() void {
    return switch (foo) {
        .MyThing => if (checkMyXYZ(xyz))
            parseAsABC(xyz)
        else if (anotherCheck(xyz))
            parseAsDEF(xyz)
        else if (isItOK(uvw)) someFunction(123) + anotherFunction(456) else if (checkerFunction(pqr)) switch (stu) {
            .SomeOtherThing => this,
            else => that,
        } else
            ghi,
        else => jkl,
    };
}

This is a zig fmt example closer to my code where I find the indentation quite unhelpful, I have to fall back to counting braces and keywords. The way it enforces the else if (...) else if (...) switch (...) { to all go on one line is also unhelpful I find. It's not obvious to me that the else ghi is the last else of the if (checkMyXYZ) and not attached to the switch (stu) without looking carefully, and it also makes for quite a long line.

Apologies for opening a bikeshedding issue, but if non-zig fmt style code is going to be a compile error it's motivating to raise these sorts of things.

With my proposed change your snippet would look like this which I think is fairly readable:

pub fn main() void {
    return switch (foo) {
        .MyThing => if (checkMyXYZ(xyz)) parseAsABC(xyz)
            else if (anotherCheck(xyz)) parseAsDEF(xyz)
            else if (isItOK(uvw)) someFunction(123) + anotherFunction(456)
            else if (checkerFunction(pqr)) switch (stu) {
                .SomeOtherThing => this,
                else => that,
            } else ghi,
        else => jkl,
    };
}

In your first example the test and the "then" expression are both short, how would the "then" expression be indented if they become too large to fit on a line?

zig fmt doesn't know or care about line length. The way it handles line breaks is driven by semantics and trailing commas. It would for example accept the following snippet no matter how long the lines were:

const variable = if (condition) std.thingo.how_long_can_you_read.TypeThing(u32, i32, std.HashMap(u32, u32)).init()
    else foobar;

If you were to insert a trailing comma into the function call, it would then give you:

const variable = if (condition) std.thingo.how_long_can_you_read.TypeThing(
            u32,
            i32,
            std.HashMap(u32, u32),
        ).init()
    else foobar;

To give another real world example from my code, this is the status quo:

pub fn next(self: *Iterator) ?*Node {
    while (self.it) |node| : (self.it = if (self.reverse) node.prev else node.next) {
        if (if (self.pending)
            self.tags & node.view.pending.tags != 0
        else
            self.tags & node.view.current.tags != 0) {
            self.it = if (self.reverse) node.prev else node.next;
            return node;
        }
    }
    return null;
}

and with the proposed changes:

pub fn next(self: *Iterator) ?*Node {
    while (self.it) |node| : (self.it = if (self.reverse) node.prev else node.next) {
        if (if (self.pending) self.tags & node.view.pending.tags != 0
            else self.tags & node.view.current.tags != 0)
        {
            self.it = if (self.reverse) node.prev else node.next;
            return node;
        }
    }
    return null;
}

I think that this is significantly more readable.

You're right that this is bikeshedding, but it is also important to get right before zig fmt stabilizes. (FWIW I don't think compile errors for zig fmt non-compliance are planned, just some indentation requirements to prevent goto-fail).

Possibly related: #3978 #4580

Yes, that style helps a lot, I'd be quite happy to have your suggestion. The example of the mismatched if and else in your example is quite jarring.

Thanks for pointing out that commas can help with the line breaking, it's a little hard to get used to. Putting a comma after 456 helps quite a bit.

We could close this issue with @ifreund's proposal as input to #4580?

It looks like behaviour changed at some point so that the else is put on the same line as the if. Is the above proposed solution still desirable?

While I think this is certainly possible it reworking the code to render else if chains to be iterative instead of recursive, this is due to elses being part of the previous else's body

Was this page helpful?
0 / 5 - 0 ratings

Related issues

dobkeratops picture dobkeratops  路  3Comments

S0urc3C0de picture S0urc3C0de  路  3Comments

DavidYKay picture DavidYKay  路  3Comments

andrewrk picture andrewrk  路  3Comments

andrewrk picture andrewrk  路  3Comments