March 29, 2021
On 3/25/21 10:28 AM, Atila Neves wrote:
> 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.

But it is a reason. If it costs nothing to return it, and doesn't HARM anything to return it, why not make it available? A tagged union is required to store the tag, so it's not like that piece of data will cease to be available.

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

I'm not sure if you have considered that `match` actually does exactly what you are saying not to do. In fact it has to. `SumType` has a dynamic type.

It's just easier to write code sometimes if you have the primitives to do it.

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

We disagree on that.

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

I'm not talking about runtime performance.

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

I don't need a profiler to tell me that checking an integer against 2 is easier for the compiler to deal with than:

1. static-foreaching over a list of templates
2. Seeing which templates match which types, which also have to be static-foreached over
3. Determining if the lambda functions can be inlined
4. Inlining said functions
5. Optimizing out the combination of all the instantiated functions into  "check this integer against 2".

>> 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 see code like this all the time:

static if(is(T == int)) { ... }

This directly maps to what one needs to do here. You are checking the type of a dynamic type against one of the possibilities (which necessarily is done at runtime). If for example, I want to only execute some code if the type is an int, then I still have to handle all the other types if I use `match`.

The code "if it's an int then execute this code" reads so much better than "for all types in the sumtype, check if the type is an int, and if so, execute this code, and if not, execute code that does nothing."

The only awkward part is using the literal `2`, which probably I would extract down to using a statcIndexOf (which I don't really like, because more meaningless work for the compiler).

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

In swift, for instance, you can use switch on a type. Something like that built into D would allow this to be much more palatable, but would probably require first-class types.

```
switch(sumtype.type)
{
case int v:
    doMyCode(v);
    break;
default:
    break;
}
```

But I still don't love it. If SumType were a compiler builtin, it would be much more appealing (then you aren't involving all sorts of template machinery to figure out which code to run, and hope the compiler optimizes it out).

The fact that the tag is there, but you can't access it is.... puzzling.

It's OK, taggedalgebraic exists, so I can just use that instead of SumType. Hopefully std.sumtype doesn't become a rarely used module, but I'll stay away from it for now.

-Steve
March 29, 2021
I agree that the process for adding new modules to Phobos could do with an overhaul, and that the community should be involved in some way, as it is for DIPs.

March 30, 2021
On Thursday, 25 March 2021 at 15:17:31 UTC, Paul Backus wrote:
> 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
>     );

This comment is not intended as criticism. SumType looks like a very good package. But, I am confused by this response.

In particular, the expectation for when such a technique would be used. Use of D's case-range statement is not a particularly advanced technique. But writing the 'restrictTo' template requires a good bit more knowledge. I doubt the intent is that people will write the equivalent of 'restrictTo' whenever coming across a 'match' use like this. But I don't see 'restrictTo' included with SumType. Is the thought that this will be rare that it won't be needed? Or perhaps, that with additional experience, such a facility might be added to SumType later? Or something much more basic that I'm missing?

Separate thing - I think the catch-all handler could be a bit better documented. Is the lack of a type that triggers it? Or is underscore ('_') special (ala Scala). If the latter, then are the '_1' and '_2' forms shown in the multiple dispatch examples special? (I'm guessing that there's nothing special about the underscore forms, but people familiar with Scala might assume some else.) I'm looking at the docs here: https://pbackus.github.io/sumtype/sumtype.SumType.html.

--Jon

March 30, 2021
On Tuesday, 30 March 2021 at 03:13:04 UTC, Jon Degenhardt wrote:
> In particular, the expectation for when such a technique would be used. Use of D's case-range statement is not a particularly advanced technique. But writing the 'restrictTo' template requires a good bit more knowledge. I doubt the intent is that people will write the equivalent of 'restrictTo' whenever coming across a 'match' use like this.

I wouldn't expect users to jump straight to the fully-generalized version, but you don't need a lot of fancy template machinery to get the same result in a specific case. For example:

    (val) {
        alias T = typeof(val);
        static assert(is(T == Foo0) || is(T == Foo1) || is(T == Foo2)
            || is(T == Foo3) || is(T == Foo4));
        doSomething1;
    }

And once you have that, it's not a huge leap to notice that you can clean it up a bit with  `staticIndexOf`. And once you do that a few times, maybe you start to think about how to generalize it.

I will grant that it may not be obvious that you can use `static assert` with `match` in this way to begin with. That's something that can be addressed by improving SumType's documentation.

> But I don't see 'restrictTo' included with SumType. Is the thought that this will be rare that it won't be needed? Or perhaps, that with additional experience, such a facility might be added to SumType later? Or something much more basic that I'm missing?

The thought is: I don't know in advance what will be common and what won't, so I think it's best to wait for feedback before adding facilities like this. In the long run, I expect we will see some utility templates like `restrictTo` added--either to the `sumtype` module, or to a more general module like `std.functional` if they stand on their own.

> Separate thing - I think the catch-all handler could be a bit better documented. Is the lack of a type that triggers it? Or is underscore ('_') special (ala Scala). If the latter, then are the '_1' and '_2' forms shown in the multiple dispatch examples special? (I'm guessing that there's nothing special about the underscore forms, but people familiar with Scala might assume some else.) I'm looking at the docs here: https://pbackus.github.io/sumtype/sumtype.SumType.html.

Your guess that the underscore is not special is correct. This should be clear from reading the documentation for match [1], but I expect there are many users who read only the examples and skip the prose, so it would be best to add a note about this to the examples as well.

[1] https://pbackus.github.io/sumtype/sumtype.match.html

March 30, 2021
On 3/30/21 12:32 PM, Paul Backus wrote:

>> Separate thing - I think the catch-all handler could be a bit better documented. Is the lack of a type that triggers it? Or is underscore ('_') special (ala Scala). If the latter, then are the '_1' and '_2' forms shown in the multiple dispatch examples special? (I'm guessing that there's nothing special about the underscore forms, but people familiar with Scala might assume some else.) I'm looking at the docs here: https://pbackus.github.io/sumtype/sumtype.SumType.html.
> 
> Your guess that the underscore is not special is correct. This should be clear from reading the documentation for match [1], but I expect there are many users who read only the examples and skip the prose, so it would be best to add a note about this to the examples as well.
> 

What might be nice is to have some simplifiers for match to prevent having to jump through the hoops.

For example, a match flavor that throws by default, or one that ignores unhandled types. This way, you don't have to write stuff like `(_) {}` at the end of your handlers.

FWIW, taggedalgebraic has `visit` to enforce all items are handled, and `tryVisit` that throws if at runtime it determines no visitors match the current type.

-Steve
March 30, 2021
On Tuesday, 30 March 2021 at 17:01:29 UTC, Steven Schveighoffer wrote:
> What might be nice is to have some simplifiers for match to prevent having to jump through the hoops.
>
> For example, a match flavor that throws by default, or one that ignores unhandled types. This way, you don't have to write stuff like `(_) {}` at the end of your handlers.
>
> FWIW, taggedalgebraic has `visit` to enforce all items are handled, and `tryVisit` that throws if at runtime it determines no visitors match the current type.

sumtype has had tryMatch [1] since version 0.4.0 (released May 2018).

For ignoring types, I have found the following utility function handy:

    void ignore(T)(auto ref T) {}

You can instantiate it explicitly to ignore a specific type:

    obj.match!(
        ignore!A,
        (B b) { doSomething1; },
        (C c) { doSomething2; }
    );

Or you can use it as a catch-all handler:

    obj.match!(
        (A a) { doSomethingWith(a); },
        ignore
    );

[1] https://pbackus.github.io/sumtype/sumtype.tryMatch.html
March 30, 2021
On 3/30/21 1:14 PM, Paul Backus wrote:
> On Tuesday, 30 March 2021 at 17:01:29 UTC, Steven Schveighoffer wrote:
>> What might be nice is to have some simplifiers for match to prevent having to jump through the hoops.
>>
>> For example, a match flavor that throws by default, or one that ignores unhandled types. This way, you don't have to write stuff like `(_) {}` at the end of your handlers.
>>
>> FWIW, taggedalgebraic has `visit` to enforce all items are handled, and `tryVisit` that throws if at runtime it determines no visitors match the current type.
> 
> sumtype has had tryMatch [1] since version 0.4.0 (released May 2018).
> 
> For ignoring types, I have found the following utility function handy:
> 
>     void ignore(T)(auto ref T) {}
> 
> You can instantiate it explicitly to ignore a specific type:
> 
>     obj.match!(
>         ignore!A,
>         (B b) { doSomething1; },
>         (C c) { doSomething2; }
>     );
> 
> Or you can use it as a catch-all handler:
> 
>     obj.match!(
>         (A a) { doSomethingWith(a); },
>         ignore
>     );
> 
> [1] https://pbackus.github.io/sumtype/sumtype.tryMatch.html


Yeah, that actually looks pretty good. Forgive my ignorance, I have not looked at any depth at SumType.

I think `ignore` would be a great addition to the library, and makes the code much easier to read. I can't think of a `match` name that would look better than just specifying ignore in the list.

One thing that is sticking out via this mechanism for pattern matching is that with lambdas, you have to name the parameter (for everything but keywords) or you will not get what you are looking for. So things like (B b) { doSomething2; } would look nicer as (B) { doSomething2; } but obviously will not do what you would expect. Hence the usage of `_` in the previous examples.

Not sure if there might be a language improvement to help with this. It probably is not that common anyway.

-Steve
March 31, 2021
On Tuesday, 23 March 2021 at 14:45:18 UTC, 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?

Simple general example is serialization and deserialization of algebraic value: without tag (kind, type index) it's not impossible but more complicated. For serialization you need write tag first (`match` is good for serialization exist value) and then deserialize you need to read tag and do deserialization for writed value type (how it must be with `match`?). If tag is private and/or it not direct point to types you need "jump through hoops" for this simple algo.
March 31, 2021
On Tuesday, 30 March 2021 at 16:32:21 UTC, Paul Backus wrote:
> I will grant that it may not be obvious that you can use `static assert` with `match` in this way to begin with. That's something that can be addressed by improving SumType's documentation.

Yeah, I would say its not obvious :) But, there does seem to be a fairly good opportunity for improving usability via documentation. As another example, the ability to define a catch-all handler is not mentioned in the text (or at least I couldn't find it). It's in the examples.

I don't know that I'll have time to propose doc updates via pull-request, but I might have time to make suggestions by some other mechanism, like issue reports something else. Is there a mechanism/venue that would work for you?

--Jon


March 31, 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);

How about e.g.:

assert(someType.has!int);

int i = someType.unwrap!int; // asserts has!int