Jump to page: 1 2
Thread overview
Improvements to switch
Apr 16
ryuukk_
Apr 16
Basile B.
2 days ago
IchorDev
2 days ago
Basile B.
1 day ago
ChloƩ
Apr 16
Meta
Apr 19
Meta
Apr 19
Meta
6 days ago
Nick Treleaven
5 days ago
ShowMeTheWay
5 days ago
ShowMeTheWay
5 days ago
ShowMeTheWay
4 days ago
Quirin Schroll
5 days ago
Dukc
April 16

It is time to make them nice to use

  • allow them as expression
int myvalue = switch(code) {
    // ...
};

  • more concise
switch(code) {
    case 1 -> "what";
    case 2, 3 -> "ok";
    else -> "no";
}
  • pattern match
switch(tagged) {
    case A, B -> do_that();
    case C myc -> do_this(myc);
    else -> nothing();
}
April 16

On Tuesday, 16 April 2024 at 10:34:21 UTC, ryuukk_ wrote:

>

It is time to make them nice to use

  • allow them as expression
int myvalue = switch(code) {
    // ...
};

About this, the main point is rather

/*-->*/ const /*<--*/ int myvalue = switch(code) {
    // ...
};

"ah finally you can define a const var decl that relies on branching" (without using the conditional expression...)

Then there's something you must think about, that's the fallback, i.e the default
clause. If you use the default clause to error you're lucky because D has noreturn now.

April 16

On Tuesday, 16 April 2024 at 10:34:21 UTC, ryuukk_ wrote:

>

It is time to make them nice to use

  • allow them as expression
int myvalue = switch(code) {
    // ...
};

  • more concise
switch(code) {
    case 1 -> "what";
    case 2, 3 -> "ok";
    else -> "no";
}
  • pattern match
switch(tagged) {
    case A, B -> do_that();
    case C myc -> do_this(myc);
    else -> nothing();
}

A couple more things:

  • branch guards
auto input = readln().strip().to!int();
auto s = switch (input) {
    case n if (n == 0) -> "zero";
    case n if (n > 0)  -> "greater than zero";
    case n if (n < 0)  -> "less than zero";
};

switch (readln().strip()) {
    case string: "" -> writeln("empty string"); // Naming the value is optional
    case string s: "asdf" -> writeln(s[2..$]);  // Prints "df"
}
  • pattern match on arrays, slices:
int[3] staticArr = [1, 2, 3];
switch (staticArr) {
   case int[N], size_t N -> writeln("Static int array of size ", N);
   case int[N], size_t N: 1 -> writeln("This branch won't be taken");
   case int[N], size_t N if (N == 1) -> writeln("Equivalent to previous branch");
   case int[2] -> writeln("This branch won't be taken either");
   case string[N], N -> writeln("Nope");
}

string[] slice = ["D", "rocks", "!"];
switch (slice) {
    case string[]  : [] -> writeln("empty slice");
    case string[] s: ["D"] -> writln(s[0]);
    case string[] s: ["D", rest...] -> writeln(rest);     // Prints ["rocks", "!"]
    case string[] s: ["D", mid..., "!"]  -> writeln(mid); // Prints "rocks"
    case string[] s: ["D", "rocks", "!"] -> writeln(s);   // Prints ["D", "rocks", "!"]
}

It'd be really nice to be able to pattern match on ranges as well, but I don't know exactly how we'd do that given how they work.

April 17

On Tuesday, 16 April 2024 at 18:25:45 UTC, Meta wrote:

>
case string: "" -> writeln("empty string"); // Naming the value is optional

Presumably we could leave off the type:

    case : "" -> writeln("empty string");

Though we might want to require an identifier or _ when there is a type.

So we'd have this grammar:

Case:
    case Pattern -> Statement
    case Type? Identifier Pattern? -> Statement
Pattern:
    `:` AssignExpression
    `if` `(` AssignExpression `)`
>

int[3] staticArr = [1, 2, 3];
switch (staticArr) {
case int[N], size_t N -> writeln("Static int array of size ", N);

Could have type inference too, like for template parameter inference.

    case T[N] _, T, alias N -> writeln("Static ", T.stringof, " array of size ", N);

Inferred symbols would be listed after matching values.

>
case string[] s: ["D", rest...] -> writeln(rest);     // Prints ["rocks", "!"]

So rest would be typed string[].

>
case string[] s: ["D", mid..., "!"]  -> writeln(mid); // Prints "rocks"

I think mid cannot be variadic in order for it to be typed string. So it would be:

    case string[] s: ["D", mid, "!"]  -> writeln(mid); // Prints "rocks"
April 17

On Tuesday, 16 April 2024 at 18:25:45 UTC, Meta wrote:

>
  • branch guards
  • pattern match on arrays, slices:

Some other things, based on section 3.3 of this C++ proposal:
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2392r2.pdf

  • Multiple alternatives that have the same result: ||

E.g.

case :9 || :15 -> "not prime";
  • Multiple constraints required for a result: &&

E.g.

switch (variant) {
    case int _ && :42 -> "int and 42";
  • Grouping common names and constraints: { }

E.g.

switch (variant) {
    case int i {
        case if (i < 0) -> "negative int";
        default -> "some other int";
    }
April 19

On Wednesday, 17 April 2024 at 11:24:16 UTC, Nick Treleaven wrote:

>

On Tuesday, 16 April 2024 at 18:25:45 UTC, Meta wrote:

>
  • branch guards
  • pattern match on arrays, slices:

Some other things, based on section 3.3 of this C++ proposal:
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2392r2.pdf

  • Multiple alternatives that have the same result: ||

E.g.

case :9 || :15 -> "not prime";

This is already covered by regular switch statements. You can write:

case 9, 15 -> "not prime";
>
  • Multiple constraints required for a result: &&

E.g.

switch (variant) {
    case int _ && :42 -> "int and 42";

I think this would require some sort of opMatch function to allow custom unpacking of arbitrary structs/classes. Ideally it would allow you to do:

switch (variant) {
    case int n if (n == 42) -> "int and 42";
    // Or alternatively
    case int n: 42 -> "int and 42";
}
>
  • Grouping common names and constraints: { }

E.g.

switch (variant) {
    case int i {
        case if (i < 0) -> "negative int";
        default -> "some other int";
    }
switch (variant) {
    // goto default is already a feature of regular switch statements
    case int i -> i < 0 ? "negative int" : goto default;
    default -> "some other int";
}

This will work if goto default is typed as noreturn. I doubt that's the case, but that's something that can also be fixed in the compiler.

April 19

On Friday, 19 April 2024 at 06:28:55 UTC, Meta wrote:

>

On Wednesday, 17 April 2024 at 11:24:16 UTC, Nick Treleaven wrote:

>

On Tuesday, 16 April 2024 at 18:25:45 UTC, Meta wrote:

>
  • branch guards
  • pattern match on arrays, slices:

Some other things, based on section 3.3 of this C++ proposal:
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2392r2.pdf

  • Multiple alternatives that have the same result: ||

E.g.

case :9 || :15 -> "not prime";

This is already covered by regular switch statements. You can write:

case 9, 15 -> "not prime";

It would be ambiguous to write e.g. case name if (cond) -> - is it matching a value name, or is name naming the switch variable?

But the grammar was me trying to extrapolate from your examples, and it might not be workable for that to be compatible with today's switch statement. Perhaps it's better to not reuse switch because we will want pattern matching with multiple statement branches, we won't always want switch to be an expression.

> >
  • Multiple constraints required for a result: &&

E.g.

switch (variant) {
    case int _ && :42 -> "int and 42";

I think this would require some sort of opMatch function to allow custom unpacking of arbitrary structs/classes. Ideally it would allow you to do:

switch (variant) {
    case int n if (n == 42) -> "int and 42";
    // Or alternatively
    case int n: 42 -> "int and 42";
}

Yes. In the C++ paper it's operator is IIRC.

> >
  • Grouping common names and constraints: { }

E.g.

switch (variant) {
    case int i {
        case if (i < 0) -> "negative int";
        default -> "some other int";
    }
switch (variant) {
    // goto default is already a feature of regular switch statements
    case int i -> i < 0 ? "negative int" : goto default;
    default -> "some other int";
}

This will work if goto default is typed as noreturn. I doubt that's the case, but that's something that can also be fixed in the compiler.

BTW that's not what my example does - the i < 0 is part of the matching, not part of the result. The difference is there can be other case statements under the first one. case if (i < 0) -> would try the next case statement when i >= 0 rather than jumping to the default case.

Also for your example I don't understand why goto default wouldn't have the same type as the result for the default branch.

April 19

On Friday, 19 April 2024 at 07:40:34 UTC, Nick Treleaven wrote:

>

On Friday, 19 April 2024 at 06:28:55 UTC, Meta wrote:

>

On Wednesday, 17 April 2024 at 11:24:16 UTC, Nick Treleaven wrote:

>

On Tuesday, 16 April 2024 at 18:25:45 UTC, Meta wrote:

>
  • branch guards
  • pattern match on arrays, slices:

Some other things, based on section 3.3 of this C++ proposal:
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2392r2.pdf

  • Multiple alternatives that have the same result: ||

E.g.

case :9 || :15 -> "not prime";

This is already covered by regular switch statements. You can write:

case 9, 15 -> "not prime";

It would be ambiguous to write e.g. case name if (cond) -> - is it matching a value name, or is name naming the switch variable?

I'm not quite sure what you mean - can you illustrate in more detail?

>

But the grammar was me trying to extrapolate from your examples, and it might not be workable for that to be compatible with today's switch statement. Perhaps it's better to not reuse switch because we will want pattern matching with multiple statement branches, we won't always want switch to be an expression.

Yeah maybe not. That was just some mock syntax off the top of my head, and it's probably not suitable for extracting a formal grammar.

> > >
  • Grouping common names and constraints: { }

E.g.

switch (variant) {
    case int i {
        case if (i < 0) -> "negative int";
        default -> "some other int";
    }
switch (variant) {
    // goto default is already a feature of regular switch statements
    case int i -> i < 0 ? "negative int" : goto default;
    default -> "some other int";
}

This will work if goto default is typed as noreturn. I doubt that's the case, but that's something that can also be fixed in the compiler.

BTW that's not what my example does - the i < 0 is part of the matching, not part of the result. The difference is there can be other case statements under the first one. case if (i < 0) -> would try the next case statement when i >= 0 rather than jumping to the default case.

I see. I think having nested case conditions might make it too complex to understand and maybe even implement.

>

Also for your example I don't understand why goto default wouldn't have the same type as the result for the default branch.

Conceptually, goto default and other constructs that transfer execution to a different part of the code should be typed as noreturn, because then you can do stuff like:

auto input = readln() || throw new Exception("empty input");

Although in this case it would actually be pretty weird... I think it would enable this type of code:

Variant v = 10;
auto str = switch (variant) {
    case int i -> i < 0 ? "negative int" : goto default;
    default -> writeln("invalid value");
};

// The only sane type for `str` is `noreturn`, and thus it should crash the program if we try to read from it.
April 22
I've thought about it for a while now. Improving switch has a lot of issues with it, such as the unusual scoping rules, the ability to goto in and out of it, the ability to interleave switch/case with other looping constructs (!).

It's unsalvageable.

It's better to create a new construct, let's say "match", and design an unconstrained syntax for it to accommodate pattern matching in particular.

("match" is already an identifier in common use, some other name would be better.)
April 23
On 23/04/2024 4:45 AM, Walter Bright wrote:
> I've thought about it for a while now. Improving switch has a lot of issues with it, such as the unusual scoping rules, the ability to goto in and out of it, the ability to interleave switch/case with other looping constructs (!).
> 
> It's unsalvageable.
> 
> It's better to create a new construct, let's say "match", and design an unconstrained syntax for it to accommodate pattern matching in particular.
> 
> ("match" is already an identifier in common use, some other name would be better.)

I was really looking forward to your recent DConf Online talk about matching.

It is why I have avoided this idea up until now, I wanted to see what you have come up with.

Same thing for sum types.

If you have any design work on this subject, I think other people not just myself would be interested in having a read.
« First   ‹ Prev
1 2