Thread overview
Sum type, the D way
Nov 29, 2022
deadalnix
Nov 29, 2022
Walter Bright
Nov 29, 2022
deadalnix
Nov 29, 2022
Walter Bright
Nov 30, 2022
deadalnix
November 29, 2022

While I think I made it fairly clear that my position is that sum types aren't what D should focus on at this time, I have the feeling this is going to be ignored, and that we are going to roll out something regardless, so I might as well put a few things here to make sure we at least end up with something that fit within a language and its spirit.

We already have a sum type like feature in D: enum. Wait what? Well enum, short for enumerations, are types that specify a set of value a variable can take. sum types are just an extension of that idea: instead of simply providing a set of values, they also allow to provide entire classes of allowable values, types.

So we could simply have:

enum MySumType {
    int,
    bool,
    MyStruct,
    // ...
}

D is a system programming language, which means one needs to be able to specify how the bits are layed down. Thanksfully, enums already provide us with a syntax to provide the backing type for our enum, and that won't be enough here. But just like ranges, we can duck type the whole thing the following way:

enum MySumType : BackingType {
    int,
    bool,
    MyStruct,
    // ...
}

// We assume that MySumType is able to generate some magic enum internally,
// such as MySumType.__Kind is an enum with the different types in there.
struct BackingType {
    MySumType.__Kind kind;
    union {
        This,
        That,
        TheOther,
    }

    // opAs is used by the compiler to extract the right union member.
    auto opAs(MySumType.__Kind T)() {
        // Return the right element in the union.
    }
}

The BackingType can be made 100% optional and compiler generated when none is provided.

This is obviously a half assed proposal, as I can already see ambiguity in the syntax thinking of it for minutes, but my goal here is for people to think a bit about what that feature looks like FOR D.

Without a coherent design we only get exploding complexity and things that don't fit together. We have an exemple of that in D currently that is definitively affecting that design (which is why we are finding ourselve looking at introducing a new feature rather than generalizing an existing one): D is completely schizophrenic about whether enum are a closed or an open set. final switch assumes closed, but binary operators such as | assume open.

Well, if forces us to answer that question (or to ignore it and add to the pile of bear traps) so I will answer it: the set of allowable value must be closed, as this is the construct that naturally extends towards supporting sum types.

But you'll ask me, we have these open set, what do we do about them? We do this:

enum CloseSet { A, B }
enum OpenSet { A, B, ... }

Voila, problem solved. When using the first one, operator like | either fall back to the base type, or plain don't work. When using the second one, final switch must, in addition to handled all the value, provide a default label.

Voila, now what about pattern matching these thing? Like this: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2392r0.pdf

November 29, 2022
My original sumtype design was indeed extending the enum. But eventually, trouble surfaced. For example, the pattern matching that goes on with types, such as for IsExpressions, and for template parameters. Suddenly, existing code that is expecting an enum would match a sumtype, likely causing problems.

Another problem is, as you mentioned, the open nature of enums, which won't work with sumtypes.

Then there's the issue of implicit casting of enums to their basetype. Not sure sumtypes are implicitly convertible.

So then it just seemed simpler with a lot fewer special cases to make sumtypes a separate entity. One problem that does resolve, is they'll be closed, which I think you'll like :-)
November 29, 2022
On Tuesday, 29 November 2022 at 18:58:59 UTC, Walter Bright wrote:
> My original sumtype design was indeed extending the enum. But eventually, trouble surfaced. For example, the pattern matching that goes on with types, such as for IsExpressions, and for template parameters. Suddenly, existing code that is expecting an enum would match a sumtype, likely causing problems.
>

I'm sure this can be sorted out.

> Another problem is, as you mentioned, the open nature of enums, which won't work with sumtypes.
>

It already doesn't work today, with things like final switch. You have the power to fix it.

This is exactly why I'm telling you to not roll out sum types right now. You are building on top of quick sands.

> Then there's the issue of implicit casting of enums to their basetype. Not sure sumtypes are implicitly convertible.
>

I don't understand what the issue is here.

> So then it just seemed simpler with a lot fewer special cases to make sumtypes a separate entity. One problem that does resolve, is they'll be closed, which I think you'll like :-)

Making the whole thing a special case doesn't typically translates into fewer special cases.

November 29, 2022
On 11/29/2022 11:40 AM, deadalnix wrote:
> On Tuesday, 29 November 2022 at 18:58:59 UTC, Walter Bright wrote:
>> My original sumtype design was indeed extending the enum. But eventually, trouble surfaced. For example, the pattern matching that goes on with types, such as for IsExpressions, and for template parameters. Suddenly, existing code that is expecting an enum would match a sumtype, likely causing problems.
> I'm sure this can be sorted out.

I'm a lot less sure. I expect a barrage of corner cases nobody expected. It's also confusing to users - they've come to expect how enums behave, and suddenly it's different.

There's also the issue of when is it an enum and when is it an enum subtype. The syntax gives it away, but in my experience, I expect endless confusion.

Calling it "sumtype" is also clarifying what it is. It is not some special enum. It's a sumtype, making it clear that D supports sumtypes. Naming things clearly is very important. People already have in their minds what an enum is, and it's not a sumtype.

Remember when we decided to use "enum" to declare manifest constants? It made a lot of sense from a programming point of view to do that, but users found it very confusing.


>> Another problem is, as you mentioned, the open nature of enums, which won't work with sumtypes.
> It already doesn't work today, with things like final switch. You have the power to fix it.

By breaking a lot of existing code, particularly the common use of flags. I would expect a lot of unhappy users.


> This is exactly why I'm telling you to not roll out sum types right now. You are building on top of quick sands.

Building a new type is not building on quick sand. It's a clean sheet.


>> Then there's the issue of implicit casting of enums to their basetype. Not sure sumtypes are implicitly convertible.
> I don't understand what the issue is here.

enums are eager to convert to their base type. sumtypes are not, in fact, the base type of a sumtype cannot be determined at compile time.


>> So then it just seemed simpler with a lot fewer special cases to make sumtypes a separate entity. One problem that does resolve, is they'll be closed, which I think you'll like :-)
> 
> Making the whole thing a special case doesn't typically translates into fewer special cases.

It makes for a much more understandable design, because the two aren't mixed up with if statements. They become separate topics.

November 30, 2022
On Tuesday, 29 November 2022 at 20:17:21 UTC, Walter Bright wrote:
> Remember when we decided to use "enum" to declare manifest constants? It made a lot of sense from a programming point of view to do that, but users found it very confusing.
>

I always foudn this confusing, but this is what it is.

> By breaking a lot of existing code, particularly the common use of flags. I would expect a lot of unhappy users.
>

Add an easy way to make these open sets. I even proposed a syntax. The alternative is unsound fundamental and having to pile up more and more new feature instead of extending existing one, because the existing ones are unsound.

>> This is exactly why I'm telling you to not roll out sum types right now. You are building on top of quick sands.
>
> Building a new type is not building on quick sand. It's a clean sheet.
>

No it is not. This new types will have to interact with all the existing types. The complexity of that approach grows quadratically. But it has the benefit of being able to ignore the problem for longer.

> enums are eager to convert to their base type. sumtypes are not, in fact, the base type of a sumtype cannot be determined at compile time.
>

Well we have opaque enums and non opaque ones already today. While I do agree this is nonsensical, it is nevertheless the current state of affair and whether we make sum type as an enum opaque or not don't make things any worse on this front.

> It makes for a much more understandable design, because the two aren't mixed up with if statements. They become separate topics.

It doesn't. While it makes for the design of sum type itself easier, it create a quadratically growing matrix of interactions. This is a bad tradeof.