A proposal: Sumtypes
Timon Gehr
timon.gehr at gmx.ch
Thu Feb 8 19:26:37 UTC 2024
On 2/8/24 16:42, Richard (Rikki) Andrew Cattermole wrote:
> Yesterday I mentioned that I wasn't very happy with Walter's design of
> sum types, at least as per his write-up in his DIP repository.
> I have finally after two years written up an alternative to it, that
> should cover everything you would expect from such a language feature.
> There are also a couple of key differences with regards to the tag and
> ABI that will make value type exceptions aka zero cost exceptions work
> fairly fast.
>
> ...
>
> ```d
> sumtype Nullable(T) {
> :none,
> T value
> }
>
> sumtype Nullable(T) = :none | T value;
>
> void accept(Nullable!Duration timeout) {}
>
> accept(1.minute);
> accept(:value = 1.minute);
> accept(:none);
> ```
> ...
>
> The first comes from Walter Bright's proposal:
>
> ```d
> sumtype Identifier (TemplateParameters) {
> @UDAs|opt Type Identifier = Expression,
> @UDAs|opt Type Identifier,
> @UDAs|opt MemberOfOperator,
> }
> ```
> ...
> TODO: swap for spec grammar version
>
> The second is short hand which comes from the ML family:
>
> ```d
> sumtype Identifier (TemplateParameters) = @UDAs|opt Type Identifier|opt
> | @UDAs|opt MemberOfOperator;
> ```
>
> TODO: swap for spec grammar version
>
> For a nullable type this would look like in both syntaxes:
>
> ```d
> sumtype Nullable(T) {
> :none,
> T value
> }
>
> sumtype Nullable(T) = :none | T value;
> ```
> ...
I have to say, I am not a big fan of having only parameterless named
constructors as a special case.
> Implicit construction and applies to types in general in any location including function arguments.
>
> ## Storage
>
> A sumtype at runtime is represented by a flexible ABI.
>
> 1. The tag [``size_t``]
> 2. Copy constructor [``function``]
> 3. Destructor [``function``]
> 4. Storage [``void[X]``]
> ...
It is not so clear why copy constructor and destructor need to be
virtual functions.
>
> The default initialization of a sumtype will always prefer ``:none`` if
> present, otherwise it is the first entry.
Just do first entry always.
> For the first entry on the short hand syntax it does not support
> expressions for the default initialization, therefore it will be the
> default initialized value of that type.
> ...
In this case, I am not sure what initializers inside a sumtype will do.
> Assigning a value to a sum type, will always prefer the currently
> selected tag.
Alarm bells go off in my head.
> If however the value cannot be coerced into the tag's type, it will then
> do a match to determine the best candidate based upon the type of the
> expression.
>
> An example of prefering the currently selected tag:
>
> ```d
> sumtype S = int i | long l;
>
> S s = :i = 2;
> ```
> ...
I would strongly advise to drop this.
> But if we switch to a larger value ``s = long.max;``, this will assign
> the long instead.
>
> ## Nullability
>
> A sum type cannot have the type state of null.
> ...
I am not sure what that means.
> ## Set Operations
>
> A sumtype which is a subset of another, will be assignable.
>
> ```d
> sumtype S1 = :none | int;
> sumtype S2 = :none | int | float;
>
> S1 s1;
> S2 s2 = s1;
> ```
> ...
This seems like a strange mix of nominal and structural typing.
> This covers other scenarios like returning from a function or an
> argument to a function.
>
> To remove a possible entry from a sumtype you must peform a match (which
> is not being proposed here):
>
> ```d
> sumtype S1 = :none | int;
> sumtype S2 = :none | int | float;
>
> S1 s1;
> S2 s2 = s1;
>
> s2.match {
> (float) => assert(0);
> (default val) s1 = val;
> }
> ```
>
> To determine if a type is in the set:
>
> ```d
> sumtype S1 = :none | int;
>
> pragma(msg, int in S1); // true
> pragma(msg, :none in S1); // true
> pragma(msg, "none" in S1); // true
> ```
> ...
I think a priori here you will have an issue with parsing.
> To merge two sumtypes together use the pipe operator on the type.
>
> ```d
> sumtype S1 = :none | int i;
> sumtype S2 = :none | long l;
> alias S3 = S1 | S2; // :none | int i | long l
> ```
> ...
The flattening behavior is unintuitive.
> Or you can expand a sumtype directly into another:
>
> ```d
> sumtype S1 = :none | int i;
> sumtype S2 = :none | S1.expand | long l; // :none | int i | long l
> ```
>
> When merging, duplicate types and names are not an error, they will be
> combined.
> Although if two names have different types this will error.
> ...
Again this mixes nominal and structural typing in a way that seems
unsatisfying. Note that different struct types do not become assignable
just because they share member types and names.
More information about the Digitalmars-d
mailing list