March 23, 2021
On Friday, 19 March 2021 at 07:28:19 UTC, drug wrote:
> On 3/18/21 9:42 PM, Oleg B wrote:
>> 
>> but read-only tag (kind, type index) is key feature in chouse between sumtype and other for me, and I hope it will be added before next compiler release...
>> 
>
> Totally agree, it is an important feature.

Can anyone explain to me why?
March 23, 2021
On 3/23/21 10:45 AM, Atila Neves wrote:
> On Friday, 19 March 2021 at 07:28:19 UTC, drug wrote:
>> On 3/18/21 9:42 PM, Oleg B wrote:
>>>
>>> but read-only tag (kind, type index) is key feature in chouse between sumtype and other for me, and I hope it will be added before next compiler release...
>>>
>>
>> Totally agree, it is an important feature.
> 
> Can anyone explain to me why?

1. It costs nothing (returning tag is pretty much free).
2. reasoning about things to do with a SumType without having to generate (possibly) an entire set of handler functions allows different designs or writing code in more efficient/straightforward ways.

Consider an assert that validates a SumType is a certain type:

SumType!(string, float, int) someType;
...
someType.match!((T t) {assert(is(T == int)); });

vs.

assert(someType.typeIndex == 2);

Which looks better? Which performs better? For the first case, all the types have to be used to build the lambda, which means 4 more functions, and the match call has to basically do a switch on all possible tags, and call the right function, which then throws an assert error or not. Or, without asserts enabled, builds 4 functions empty functions which hopefully are inlined.

The second implementation checks if an integer equals 2. Or if asserts are disabled, elides the whole statement. Only drawback I see is it's very dependent on the type not changing indexes.

All that said, the taggedalgebraic project looks nicer to me:

union Values
{
   string String;
   float Float;
   int Integer;
}

TaggedAlgebraic!(Values) algType;
...

assert(algType.kind == algType.Kind.Integer);

-Steve
March 23, 2021
On Tuesday, 23 March 2021 at 16:29:52 UTC, Steven Schveighoffer wrote:
>
> Consider an assert that validates a SumType is a certain type:
>
> SumType!(string, float, int) someType;
> ...
> someType.match!((T t) {assert(is(T == int)); });
>
> vs.
>
> assert(someType.typeIndex == 2);

Well, first, we would never write a naked magic number like this in production code, so for the sake of a fair comparison, let's fix that issue:

    assert(someType.typeIndex == staticIndexOf!(int, someType.Types));

Moving on--if I were doing this often enough for it to matter, I would define a helper function:

    bool contains(T, ST)(ST st)
    if (isSumType!ST)
    {
        return st.match!(value => is(typeof(value) == T));
    }

Usage:

    assert(someType.contains!int);

Personally I think this is far preferable to the typeIndex version in terms of readability. In terms of performance: I checked the generated assembly, and `ldc -O` inlines everything, but `dmd -O -inline` does not, so if you are using DMD you're leaving performance on the table here (but isn't that true in general?). There is also some additional compile-time overhead from instantiating the templates.
March 25, 2021
On Tuesday, 23 March 2021 at 16:29:52 UTC, Steven Schveighoffer wrote:
> On 3/23/21 10:45 AM, Atila Neves wrote:
>> On Friday, 19 March 2021 at 07:28:19 UTC, drug wrote:
>>> On 3/18/21 9:42 PM, Oleg B wrote:
>>>>
>>>> but read-only tag (kind, type index) is key feature in chouse between sumtype and other for me, and I hope it will be added before next compiler release...
>>>>
>>>
>>> Totally agree, it is an important feature.
>> 
>> Can anyone explain to me why?
>
> 1. It costs nothing (returning tag is pretty much free).

I don't think this is enough of a reason.

> 2. reasoning about things to do with a SumType without having to generate (possibly) an entire set of handler functions allows different designs or writing code in more efficient/straightforward ways.

This is the part I don't get. Why would one want to do that? To me this feels like checking for the dynamic type of an object in OOP, in the sense that if you're doing that you're probably doing something wrong.

> Consider an assert that validates a SumType is a certain type:
>
> SumType!(string, float, int) someType;
> ...
> someType.match!((T t) {assert(is(T == int)); });
>
> vs.
>
> assert(someType.typeIndex == 2);
>
> Which looks better?

The former.

> Which performs better?

It shouldn't matter - performance considerations in the absence of running a profiler are futile. If nobody notices, it doesn't matter. If someone noticed, a profiler gets run and the code optimised where it's actually slow.

Having said that, I think that if there are two alternatives to getting the job done, and the two are equally readable/maintainable, prefer the fastest one. Or as Sutter/Alexandrescu once wrote, "belated pessimization is the root of no good".

 For the first case,
> all the types have to be used to build the lambda, which means 4 more functions, and the match call has to basically do a switch on all possible tags, and call the right function, which then throws an assert error or not. Or, without asserts enabled, builds 4 functions empty functions which hopefully are inlined.
>
> The second implementation checks if an integer equals 2. Or if asserts are disabled, elides the whole statement. Only drawback I see is it's very dependent on the type not changing indexes.
>
> All that said, the taggedalgebraic project looks nicer to me:
>
> union Values
> {
>    string String;
>    float Float;
>    int Integer;
> }
>
> TaggedAlgebraic!(Values) algType;
> ...
>
> assert(algType.kind == algType.Kind.Integer);

If one wants to do that sort of thing, yes, this is nicer. I'm still at a loss as to why one would *want* to do such a thing.

I think it's useful to remember that in languages where sum types and pattern matching are features there's no way to do this.


March 25, 2021
I think that `kind` vs `handler` is more like old-school vs modern style. They are equal in general. Modern compilers are good in optimization. I chose TaggedAlgebraic for my projects about 7 years ago and don't remember the exact reason I was need for kind feature. In general I agree to Steven. I can add to his post that kind lets you do:
```D
	switch(kind)
	{
	case Kind.foo0..case Kind.foo5:
		doSomething1;
		break;
	case Kind.bar:
	case Kind.baz:
	case Kind.foobar:
		doSomething2;
		break;
	default:
		doDefaultThings;
	}
```
using handlers:
```D
	someType.match!(
		(Foo0 foo) => doSomething1,
		(Foo1 foo) => doSomething1,
		(Foo2 foo) => doSomething1,
		(Foo3 foo) => doSomething1,
		(Foo4 foo) => doSomething1,
		(Bar  bar) => doSomething2,
		(Baz  baz) => doSomething2,
		(FooBar fb) => doSomething2,
		(_) => doDefaultThings;
	);
```
These examples are verbose equally but the first version contains less boilerplate.

I believe both approaches are good. They complement each other.
March 25, 2021
On Thursday, 25 March 2021 at 14:28:06 UTC, Atila Neves wrote:
> If one wants to do that sort of thing, yes, this is nicer. I'm still at a loss as to why one would *want* to do such a thing.
>
> I think it's useful to remember that in languages where sum types and pattern matching are features there's no way to do this.

I suspect the big reason is that a lot of D programmers aren't super familiar with these languages, or with functional programming in general, so they find SumType's match function less comfortable and approachable than switching on an enum (even though it's fundamentally the same thing).

There's a great illustration of this in a forum thread from last year [1]. Once I showed how to convert the code to use SumType and match [2], the author could understand it, but they weren't able to come up with it on their own.

[1] https://forum.dlang.org/post/kjozzeynentyrarssnzz@forum.dlang.org
[2] https://forum.dlang.org/post/bzueywubiazbkxvqnaid@forum.dlang.org
March 25, 2021
On Thursday, 25 March 2021 at 15:03:37 UTC, drug wrote:
> In general I agree to Steven. I can add to his post that kind lets you do:
> ```D
> 	switch(kind)
> 	{
> 	case Kind.foo0..case Kind.foo5:
> 		doSomething1;
> 		break;
> 	case Kind.bar:
> 	case Kind.baz:
> 	case Kind.foobar:
> 		doSomething2;
> 		break;
> 	default:
> 		doDefaultThings;
> 	}
> ```
> using handlers:
> ```D
> 	someType.match!(
> 		(Foo0 foo) => doSomething1,
> 		(Foo1 foo) => doSomething1,
> 		(Foo2 foo) => doSomething1,
> 		(Foo3 foo) => doSomething1,
> 		(Foo4 foo) => doSomething1,
> 		(Bar  bar) => doSomething2,
> 		(Baz  baz) => doSomething2,
> 		(FooBar fb) => doSomething2,
> 		(_) => doDefaultThings;
> 	);
> ```
> These examples are verbose equally but the first version contains less boilerplate.

Only if you don't know what you're doing. Check this out:

    template restrictTo(Args...)
        if (Args.length >= 1)
    {
        import std.meta : IndexOf = staticIndexOf;

        alias Types = Args[0 .. $-1];
        alias fun = Args[$];

        auto ref restrictTo(T)(auto ref T value)
            if (IndexOf!(T, Types) >= 0);
        {
            import core.lifetime: forward

            static assert(IndexOf!(T, Types) >= 0);
            return fun(forward!value);
        }
    }

Usage:

    someType.match!(
        restrictTo!(Foo0, Foo1, Foo2, Foo3, Foo4,
            val => doSomething1
        ),
        restrictTo!(Bar, Baz, FooBar,
            val => doSomething2
        ),
        _ => doDefaultThings
    );
March 25, 2021
On 3/25/21 6:17 PM, Paul Backus wrote:
> Usage:
> 
>      someType.match!(
>          restrictTo!(Foo0, Foo1, Foo2, Foo3, Foo4,
>              val => doSomething1
>          ),
>          restrictTo!(Bar, Baz, FooBar,
>              val => doSomething2
>          ),
>          _ => doDefaultThings
>      );

Looks good
March 26, 2021
On Thursday, 25 March 2021 at 15:05:40 UTC, Paul Backus wrote:
> On Thursday, 25 March 2021 at 14:28:06 UTC, Atila Neves wrote:

>> I think it's useful to remember that in languages where sum types and pattern matching are features there's no way to do this.
>
> I suspect the big reason is that a lot of D programmers aren't super familiar with these languages, or with functional programming in general, so they find SumType's match function less comfortable and approachable

Completely plausible but in my opinion not nearly enough of a reason to offer an escape hatch.

> than switching on an enum (even though it's fundamentally the same thing).

Matching is like *final* switching on the enum, except that's that the only thing you can do so you can't screw up.


March 26, 2021
On Friday, 26 March 2021 at 02:56:29 UTC, Atila Neves wrote:
> Completely plausible but in my opinion not nearly enough of a reason to offer an escape hatch.

IMO typeIndex is not really an "escape hatch", because while you can write:

    if (sumTypeInstance.typeIndex == 2) {
        // ...
    }

...there is still nothing you can do inside the `if` body to access the value without going through `match`. To follow the OOP analogy, it would be like having access to `instanceof` but not downcasts--you can check what the runtime type is, but you still need to go through the normal runtime dispatch mechanism to do anything with the object.

I think this principle--that you can't touch the SumType's value without using `match`--is a good place to draw the line on what we're willing to allow in SumType's public API and what we're not, but I am happy to hear arguments for other positions. Perhaps we can talk about this during the upcoming BeerConf.