November 29, 2022
On 11/29/22 08:26, Walter Bright wrote:
> On 11/28/2022 6:53 PM, Adam D Ruppe wrote:
>> Curious, what did you find lacking in std.sumtype?
>>
>> Same question to Walter.
> 
> It's addressed in the draft DIP I just posted.
> 
> https://github.com/WalterBright/DIPs/blob/sumtypes/DIPs/1NNN-(wgb).md
> 
> I did email std.sumtype's author, Paul Backus, for his observations but have not heard back yet.

Nice! I think this general design, where it behaves just like a tagged union but @safe, makes some sense for D. I _really_ wish bad element access resulted in a compile-time error instead of a runtime error though.

Basically, you could treat

if(?s.member){

}

and

assert(?s.member);

specially, and only allow accesses to s.member if they are guarded by one of them. By default, accessing members is disallowed. The user can then choose between:

if(?s.member){
    writeln(s.member);
}

and

assert(?s.member);
writeln(s.member);

To either check the tag manually or opt into the runtime error very explicitly.

I think catching errors early during type checking is one of the most compelling things about sum types in other languages, and it would be great if D could get that in some way. The analysis does not have to be particularly sophisticated. In particular, no control flow analysis is required.
November 29, 2022
On 11/29/22 08:26, Walter Bright wrote:
> On 11/28/2022 6:53 PM, Adam D Ruppe wrote:
>> Curious, what did you find lacking in std.sumtype?
>>
>> Same question to Walter.
> 
> It's addressed in the draft DIP I just posted.
> 
> https://github.com/WalterBright/DIPs/blob/sumtypes/DIPs/1NNN-(wgb).md
> 
> I did email std.sumtype's author, Paul Backus, for his observations but have not heard back yet.

Some things that are missing:

- non-default construction, e.g.:

sumtype Result{
    error,
    int value;
}

auto result1 = Result.error;
auto result2 = Result.value(2);

The above is a bit unergonomic, maybe there is a better way to expose those constructors. They should exist though, otherwise it is e.g., impossible to initialize an immutable sumtype.


- introspection, e.g. is(T==sumtype) and __traits(allMembers, ...)

- in particular, it would be nice to provide access to the associated enumeration and the tag, e.g. for Result above, there could be an automatically generated enum like this:

enum Result{
    error,
    value,
}

Then you could do something like:

final switch(tag(result)) {
     case Tag!Result.error: ...
     case Tag!Result.value: ...
}

This way, sumtype can be a drop-in replacement for existing unsafe tagged unions. I guess with __traits(allMembers, ...) this can be done in the library, but nothing beats direct access to the tag.

- how does it interact with type qualifiers? In particular, I guess you can have a sumtype with only immutable members and reassign them anyway?


- pattern matching (though that's acknowledged in the DIP and can probably be designed later)

November 29, 2022
On 11/29/22 08:26, Walter Bright wrote:
> On 11/28/2022 6:53 PM, Adam D Ruppe wrote:
>> Curious, what did you find lacking in std.sumtype?
>>
>> Same question to Walter.
> 
> It's addressed in the draft DIP I just posted.
> 
> https://github.com/WalterBright/DIPs/blob/sumtypes/DIPs/1NNN-(wgb).md
> 
> I did email std.sumtype's author, Paul Backus, for his observations but have not heard back yet.

BTW: I understand extraordinarily well where the desire for supporting an explicitly nullable pointer comes from, but I worry that integrating it into the more general syntax implicitly is a bit too cute. It will lead to bad interactions in generic code.

E.g.:

sumtype WithDefault(T){
    Default;
    T;
}

A user does not expect this to behave specially if `T` is a pointer. Generic code would have to explicitly check for that case and manually undo the rewrite in the library. I don't want to read the resulting unwieldy Phobos code.

Explicitly nullable pointers are too good of a feature to just give up though, so instead, you could introduce explicit syntax for the null check.

E.g.:

sumtype Nullable{
    Null,
    int* Ptr;
    invariant(Ptr !is null);
}

The semantics of this would be to check the invariant on assignment and if it fails, then default-initialize the type instead. There could be multiple invariants that each can only refer to one of the members.

Then this would be distinct from:

sumtype DoublyNullable{
    Null,
    int* Ptr;
}

It would also be more general because it allows enforcing more general invariants. (But it could also be limited to the special case with null, at least in the beginning; what's important is that the different behavior is documented explicitly in a difference in syntax.)

Note that with the semantics where a bad member access is a runtime error, you don't gain much over just using a raw pointer. Therefore, I really think D should statically enforce that the user checks the tag.
November 29, 2022
On 11/29/22 15:01, Timon Gehr wrote:
> no control flow analysis is required.

Meant to write "data flow analysis" here.
November 29, 2022
On 11/29/22 15:18, Timon Gehr wrote:
> 
> sumtype WithDefault(T){
>      Default;
>      T;
> }

Should have been:

sumtype WithDefault(T){
    Default,
    T value,
}
November 29, 2022
On 11/29/22 08:26, Walter Bright wrote:
> On 11/28/2022 6:53 PM, Adam D Ruppe wrote:
>> Curious, what did you find lacking in std.sumtype?
>>
>> Same question to Walter.
> 
> It's addressed in the draft DIP I just posted.
> 
> https://github.com/WalterBright/DIPs/blob/sumtypes/DIPs/1NNN-(wgb).md
> 
> ...

Maybe consider changing the syntax to something like:

sumtype ST{
    a;
    int* b;
}

The reason is that with comma-separated values, metaprogramming is hobbled.

I think we really want to be able to do things like:

sumtype ST(bool hasC){
    a;
    int* b;
    static if(hasC){
        float c;
    }
}

Similar for `static foreach`. The fact that this does not work for `enum`s is among the most annoying limitations of `enum`s.
November 30, 2022
On 30/11/2022 3:24 AM, Timon Gehr wrote:
> Maybe consider changing the syntax to something like:
> 
> sumtype ST{
>      a;
>      int* b;
> }
> 
> The reason is that with comma-separated values, metaprogramming is hobbled.
> 
> I think we really want to be able to do things like:
> 
> sumtype ST(bool hasC){
>      a;
>      int* b;
>      static if(hasC){
>          float c;
>      }
> }
> 
> Similar for `static foreach`. The fact that this does not work for `enum`s is among the most annoying limitations of `enum`s.

Could you please post this on the new thread where the draft is linked?

It is a good feedback and in this thread it'll get lost.
November 29, 2022
On 11/29/22 14:30, deadalnix wrote:
> 
> Yes, and what I'm telling you is that the problems who'd deserve to be solved here are not specific to sum types, at least, not the worse offenders.

I am also in favor of making solutions that are needed for sum types available to user-defined types to a good extent. But I do think some new solutions are needed, also on the front of the grammar.

> Therefore, baking a special case solution for sum type would:
> 1/ Not solve the core issue to begin with.

Together with tuples, it solves the issue that there are no algebraic data types. I think this is significant and no pure library solution can match the ergonomics of a well-designed built-in solution.

> 2/ Make the core issue harder to solve, because it now has to be compatible with whatever is done for sum types.

Ideally that's not a constraint because it will be done right for sum types first and can then inform a more general solution.

> 3/ Increase language complexity, which means more bugs and other problem in practice.
> 4/ Tooling problems.

Fair points.
November 29, 2022
On 11/29/22 15:31, rikki cattermole wrote:
> On 30/11/2022 3:24 AM, Timon Gehr wrote:
>> Maybe consider changing the syntax to something like:
>>
>> sumtype ST{
>>      a;
>>      int* b;
>> }
>>
>> The reason is that with comma-separated values, metaprogramming is hobbled.
>>
>> I think we really want to be able to do things like:
>>
>> sumtype ST(bool hasC){
>>      a;
>>      int* b;
>>      static if(hasC){
>>          float c;
>>      }
>> }
>>
>> Similar for `static foreach`. The fact that this does not work for `enum`s is among the most annoying limitations of `enum`s.
> 
> Could you please post this on the new thread where the draft is linked?
> 
> It is a good feedback and in this thread it'll get lost.

Oops. Moved it all over. I guess the corresponding messages in this thread can be deleted.
November 29, 2022
On Tuesday, 29 November 2022 at 02:53:27 UTC, Adam D Ruppe wrote:
> On Tuesday, 29 November 2022 at 02:33:21 UTC, deadalnix wrote:
>> I'm literally building a sum type
>
> Curious, what did you find lacking in std.sumtype?
>
> Same question to Walter.
>
> (btw I've never used it myself but this seems an obvious question that needs to be answered by anyone doing their own implementation, in or out of the language)

I haven't used it yet. But you can't implicitly convert an element type instance into a sum type struct instance. E.g. passing an int to a sum type struct function parameter.