November 29, 2022

On Tuesday, 29 November 2022 at 06:26:20 UTC, Walter Bright wrote:

>

Go ahead, Make My Day! Destroy!

https://github.com/WalterBright/DIPs/blob/sumtypes/DIPs/1NNN-(wgb).md

Regarding

sumtype Result
{
    Error,
    int Value
}

, is

Result res;
res = 25;

supposed to be supported aswell? If so, why not --- because it requires implicit conversion?

November 29, 2022

On Tuesday, 29 November 2022 at 06:26:20 UTC, Walter Bright wrote:

>

Go ahead, Make My Day! Destroy!

https://github.com/WalterBright/DIPs/blob/sumtypes/DIPs/1NNN-(wgb).md

ABI

I think a section on ABI is missing. It should specify the memory layout of sumtypes, just as for existing language constructs [1]. What's even more important than the memory layout is the value of the tag. Something needs to be specified in regards to the value of the tag. One needs to be able to draw some conclusions about what happens with the value if members are added, removed or reordered. Ideally the tag value of existing members should not change. These are important things when using sumtypes at ABI boundaries.

Checking of the Tag

The DIP mention there's a runtime check of the tag, but it doesn't specify what "runtime check" is. It should. Walter did reply to a post that it would be like a buffer overflow error [2]. Which is not entirely clear, but if you overflow a buffer (an array) it throws a RangeError, so I would guess it throws an Error. Yet another language construct that a has a non-obvious dependency on the exception handling system. This is unfortunate. The sumtype is a good alternative for error handling to the existing exception system we have today. If the sumtype is going to depend on the exception system this will be less attractive. A tagged union can be used in any system (embedded, kernel, etc.). If the sumtype is going to depend on the exception system it probably cannot be used in these environments.

It would be great if there was no hidden runtime check. Force the user explicitly write code that checks the tag, otherwise it's a compile time error to access a member. Timon already suggested this [3].

This could potentially be extended to allow accessing the field without checking the tag, but require checking the tag before using the value, example:

sumtype Result
{
    Error,
    int Value
}

Result foo();

void bar()
{
    auto value = foo().Value; // ok, has not been used yet
    writeln(value); // compile time error to use `value`
    if (!?value)
        return;
    writeln(value); // ok, `value` has been checked above
}

This would allow early returns and avoid nesting all code in if statements. BTW, value now behaves more or less like an optional type.

Breaking Changes

Another breaking change, perhaps an edge case, but code that inspects all types and language constructs and performs different actions, i.e. a serialization library or something like writeln. Example:

static if (is(T == int)) {}
else static if (is(T == enum)) {}
// exhaustive checks of all language constructs
else
    static assert(false);

Or even worse, if the else statement is missing.

Switch Statement/Pattern Matching

The DIP mentions that pattern matching is subject of another DIP. But I think it's reasonable that the switch statement should support sumtypes, in the same way as the if statement. Nothing fancy, just the same feature as the if statement supports.

[1] https://dlang.org/spec/abi.html
[2] https://forum.dlang.org/post/tm4kbe$23l2$1@digitalmars.com
[3] https://forum.dlang.org/post/tm55o9$mq2$3@digitalmars.com

--
/Jacob Carlborg

November 29, 2022
On Tue, Nov 29, 2022 at 08:37:57PM +0000, Jacob Carlborg via Digitalmars-d wrote: [...]
> ### ABI
> 
> I think a section on ABI is missing. It should specify the memory layout of sumtypes, just as for existing language constructs [1].

Agreed.


> What's even more important than the memory layout is the value of the tag. Something needs to be specified in regards to the value of the tag. One needs to be able to draw some conclusions about what happens with the value if members are added, removed or reordered. Ideally the tag value of existing members should not change. These are important things when using sumtypes at ABI boundaries.

You can't dynamically change a sumtype (add/remove/reorder its constituents) after the fact, because that may change their size, which implies an ABI change.  I think it's pretty much a given that you have to recompile after any change to a sumtype's definition.  (Just like you have to recompile when you change a struct's definition -- since its size may no longer be compatible with its old definition.)


T

-- 
Knowledge is that area of ignorance that we arrange and classify. -- Ambrose Bierce
November 29, 2022
On 11/29/22 21:50, H. S. Teoh wrote:
>> What's even more important than the memory layout is the value of the
>> tag. Something needs to be specified in regards to the value of the
>> tag. One needs to be able to draw some conclusions about what happens
>> with the value if members are added, removed or reordered. Ideally the
>> tag value of existing members should not change. These are important
>> things when using sumtypes at ABI boundaries.
> You can't dynamically change a sumtype (add/remove/reorder its
> constituents) after the fact, because that may change their size, which
> implies an ABI change.  I think it's pretty much a given that you have
> to recompile after any change to a sumtype's definition.  (Just like you
> have to recompile when you change a struct's definition -- since its
> size may no longer be compatible with its old definition.)

I guess the point is that in some contrast to structs, it may well be compatible in some cases, and it is important to know in which cases it remains compatible.
November 29, 2022

On Tuesday, 29 November 2022 at 18:52:58 UTC, ryuukk_ wrote:

>
sumtype T {
}

consistency is key

Agreed.

November 29, 2022
As I expected, the threads on sum types have a lot of posts, and each reply I make spawns many more. I apologize if I don't respond in depth to all of them.
November 29, 2022
On 11/29/2022 6:42 AM, Timon Gehr wrote:
> Nice!

Wow! I expected you to eviscerate it! :-)

> I think this general design, where it behaves just like a tagged union but @safe, makes 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 expect that when a user accesses s.member, he implicitly expects it to be that member. If it isn't, then it's a program bug and hence a fatal runtime error. Adding an assert() in front of it is redundant.


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

The compiler could do:

    writeln(s.a);
    writeln(s.b); // compile time error

with data flow analysis, because if all paths to the second use go through reading s.a, then s.b could not possibly be valid. It would be similar to:

    if (x == 0)
    {
        if (x == 1) deadCode();
    }

which currently dmd does not do.
November 29, 2022
On 11/29/2022 6:45 AM, Timon Gehr wrote:

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

Ok


> - introspection, e.g. is(T==sumtype)

Already in the DIP

> and __traits(allMembers, ...)

Ok


> - 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 propose a (future) match statement instead. Sumtypes and match statements are symbiotically joined at the hip. I realize this proposal is somewhat crippled without the match, but I didn't want to put the time into designing the match if sumtypes are DOA.


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

An immutable sumtype could not have its members reassigned.

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

Yes.

November 30, 2022
On Tuesday, 29 November 2022 at 20:18:02 UTC, H. S. Teoh wrote:
> Supporting ctors, postblits, and dtors does increase the complexity of the implementation.  Perhaps that can be left as a future extension?  In any case, eventually we should support it; otherwise sumtypes will be too limited in their usefulness.

Such support is already implemented in std.sumtype, so you can review the code yourself if you would like to see how much complexity it adds to the implementation.

Off the top of my head, I can tell you that most of the code volume comes from having to copy and paste each function 4 times to handle different mutability qualifiers (mutable, const, immutable, inout). Which is tedious (and a symptom of a missing language feature IMO), but not exactly complex.
November 29, 2022
On 11/29/2022 12:37 PM, Jacob Carlborg wrote:
> ### ABI

Section added.


> ### Checking of the Tag
> 
> The DIP mention there's a runtime check of the tag, but it doesn't specify what "runtime check" is.

Section added.


> This could potentially be extended to allow accessing the field without checking the tag, but require checking the tag before using the value, example:

Discussed in a reply to Timon.


> ### Breaking Changes
> 
> Another breaking change, perhaps an edge case, but code that inspects all types and language constructs and performs different actions, i.e. a serialization library or something like `writeln`.

Added

> ### Switch Statement/Pattern Matching
> 
> The DIP mentions that pattern matching is subject of another DIP. But I think it's reasonable that the switch statement should support sumtypes, in the same way as the if statement.

A match statement would make this redundant. In fact, I expect the match statement to be lowered to a switch :-)