April 22
On 4/22/2024 10:33 AM, Richard (Rikki) Andrew Cattermole wrote:
> 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.

Sumtypes must come before pattern matching. I had a proposal for it:

https://www.digitalmars.com/d/archives/digitalmars/D/sumtypes_for_D_366242.html
April 23

On Friday, 19 April 2024 at 19:27:22 UTC, Meta wrote:

>

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?

Where you had:

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

I understand that in the last case, it's giving input an alternative name n, then testing n < 0.
But suppose n is already a symbol in scope, and the test might not involve n:

case n if (p < 0)

I might expect that to match when input is equal to the value of n and p is negative. But how does the parser know if n is supposed to be an alternative name for input, or to be a value looked up in the current scope. It can be figured out at semantic time, but I think it would be better to design the syntax to be unambiguous at the parser stage about whether it is introducing a new symbol or not.

> >

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.

I think it would be straightforward, but indeed could wait until later.

> >

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.

I would expect the default case to produce a value unless it does not terminate. So I would require writing an assert(0) at the end of it.

April 24
On Monday, 22 April 2024 at 16:45:35 UTC, 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.)

int x = 1;

check x {
    1 => writeln!("one"),
    2 => writeln!("two"),
    3 => writeln!("three"),
    4 => writeln!("four"),
    5 => writeln!("five"),
    _ => writeln!("something else"),
}

April 24
On Wednesday, 24 April 2024 at 06:52:51 UTC, ShowMeTheWay wrote:
> On Monday, 22 April 2024 at 16:45:35 UTC, 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.)
>
> int x = 1;
>
> check x {
>     1 => writeln!("one"),
>     2 => writeln!("two"),
>     3 => writeln!("three"),
>     4 => writeln!("four"),
>     5 => writeln!("five"),
>     _ => writeln!("something else"),
> }

aka ..a new language construct (i.e. an improvement over a switch expression) called 'a check expression'.

April 24

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

>

...
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";

...`

the 'switch' keyword implies old-school.. D needs a new term.
and the 'case' word is superfluous.

something like this perhaps:

int x = 15;

check x {
    < 0  _    => writeln!("Sorry. That's not even possible!"),
    0 | 1     => writeln!("wtf!"),
    2..5      => writeln!("mmm.. not too bad"),
    6..10     => writeln!("that's a bit better"),
    11..15    => writeln!("now you're getting it"),
    20 | >20  => writeln!("Wow! I think you've got it now"),
    _ => writeln!("Oops! Something's not right."),
}
April 24

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
  • more concise
  • pattern match

I'm certainly open to exploring the general notion. I think Walter is too, at least regarding the last one, as I recall he has said somewhere that D will have pattern matching.

However, whether we actually want to change switch depends on the details, exactly how are we going to do it? Every new langauge feature has to consider added language complexity, corner cases and backwards compatibility questions. How hard those are to deal with, compared to the benefit, is the ultimate test of worth for the feature.

I somewhat tend to agree with Walter. switch works how it works thanks to C heritage, as a result it has a lot of backwards compatibility burden. Any attempt to overhaul it to support pattern matching as a primary use case is probably going to complicate the language more than just designing a new construct.

But I'm not locked to this position - if someone presents a clever switch upgrade proposal along with the details, I'd be happy to be proven wrong.

April 25

On Monday, 22 April 2024 at 16:45:35 UTC, 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.)

Why not do it as C# does and put switch after the expression?
There, it’s something like string[1..$-1] switch { … }.

April 27

On Tuesday, 16 April 2024 at 16:00:48 UTC, Basile B. wrote:

>

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

You can already do that.

const string myValue = (){
	switch(code){
		case 1:    return "what";
		case 2, 3: return "ok";
		default:   return "no";
	}
}();
April 27

On Saturday, 27 April 2024 at 14:58:19 UTC, IchorDev wrote:

>

On Tuesday, 16 April 2024 at 16:00:48 UTC, Basile B. wrote:

>

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

You can already do that.

const string myValue = (){
	switch(code){
		case 1:    return "what";
		case 2, 3: return "ok";
		default:   return "no";
	}
}();

Sure but the matching AST is unnecessarily complex. A function literal, plenty of return statements, at least 1 capture. Consequently the path borrowed by the compiler is much more complex, it has to do things that would not be done with the expression: return type inference, block exits, etc. Then without optimizations enabled that does not have the equivalent runtime performances.

April 28
On 4/28/24 01:58, Basile B. wrote:
> On Saturday, 27 April 2024 at 14:58:19 UTC, IchorDev wrote:
>> On Tuesday, 16 April 2024 at 16:00:48 UTC, Basile B. wrote:
>>> About this, the main point is rather
>>>
>>> ```d
>>> /*-->*/ const /*<--*/ int myvalue = switch(code) {
>>>     // ...
>>> };
>>> ```
>>>
>>> "ah finally you can define a const var decl that relies on branching" (without using the conditional expression...)
>>
>> You can already do that.
>> ```d
>> const string myValue = (){
>>     switch(code){
>>         case 1:    return "what";
>>         case 2, 3: return "ok";
>>         default:   return "no";
>>     }
>> }();
> 
> Sure but the matching AST is unnecessarily complex. A function literal, plenty of return statements, at least 1 capture. Consequently the path borrowed by the compiler is much more complex, it has to do things that would not be done with the expression: return type inference, block exits, etc. Then without optimizations enabled that does not have the equivalent runtime performances.
> 

Specifically promoting switch to an expression would be a wasted opportunity to generalize this to other types of statements, such as try statements.

Here are some examples in pseudo-D of how that could be useful. In these examples, a hypothetical do keyword could prefix any statement and turn it into an expression, and a yield statement would provide the result of evaluating that expression:

    // no need to (default-)initialize file first
    auto file = do try {
        yield open(path);
    } catch (ErrnoException ex) {
        if (ex.errno == ENOENT)
            return;  // return from caller
        throw ex;
    };
    // use file without catching ErrnoException
    file.write("hello");

Or foreach statements:

    const value = do {
        foreach (item; someOpApplyStruct)
            if (f(item))
                yield item;
        throw new NotFoundError;
    };

If the do-ed statement would run off the end (as with a non-void function lacking a return statement) that would be an error.

Besides initializing variables, another advantage of promoting statements to expressions directly instead of through a called lambda is that you can have control flow out of the expression into another statement, such as by break, continue, or return statements.