Thread overview
6 days ago

In this proposal I propose a third sumtype design, one in which it is based upon structs.
Paul's design is based upon union's, and Walter's is on enum's.
This completes the triple axis that sumtypes live on.

Latest: https://gist.github.com/rikkimax/35b5673554e8d15fc3710082953989fb

Current: https://gist.github.com/rikkimax/35b5673554e8d15fc3710082953989fb/bdf1ff8fd342c248bd42902d4cf032ab99496449

Matching is supported by this proposal, with full capability. It uses DIP1048, along with another proposal in the development forum the member-of-operator.

Tag names are optional, if you use the member-of-operator to define an element you cannot provide a name as it is already unique.

The layout of the sumtype is variable size, allowing for copy constructors, destructors and no value (if size zero).

There is also one other unique behaviour, the ability to act as an alias this, when only one element is in the constraint set. Very useful behaviour for returning values with a match.

I do have three core goals in this design:

  1. Make it very easy to swap from a library design to a language sumtype.
  2. Allow value type exceptions to be baked by this, and to offer catch all.
  3. Easy for non-D programmers to learn and utilize this as a feature without changing their mental model. As a concept this has changed very little over the last 50 years, we do not need to invent a new set of design parameters.
sumtype S = :None | int;
sumtype S2 = int | bool;

S1 s1;
S2 s2 = s1.match {
	(:None) {
		if (random() > 0.5)
			return true;
		else
			return 2;
	};

	(int v) {
		return v;
	};
};

To access an element value in @system code use casting. Assigning is @safe.

sumtype S = int i | bool;
S s;

cast(:i)s = 2;
assert(cast(:i)s == 2);
6 days ago

On Thursday, 12 September 2024 at 11:28:56 UTC, Richard (Rikki) Andrew Cattermole wrote:

>

In this proposal I propose a third sumtype design, one in which it is based upon structs.
Paul's design is based upon union's, and Walter's is on enum's.
This completes the triple axis that sumtypes live on.

Latest: https://gist.github.com/rikkimax/35b5673554e8d15fc3710082953989fb

Current: https://gist.github.com/rikkimax/35b5673554e8d15fc3710082953989fb/bdf1ff8fd342c248bd42902d4cf032ab99496449

Matching is supported by this proposal, with full capability. It uses DIP1048, along with another proposal in the development forum the member-of-operator.

Tag names are optional, if you use the member-of-operator to define an element you cannot provide a name as it is already unique.

The layout of the sumtype is variable size, allowing for copy constructors, destructors and no value (if size zero).

There is also one other unique behaviour, the ability to act as an alias this, when only one element is in the constraint set. Very useful behaviour for returning values with a match.

I do have three core goals in this design:

  1. Make it very easy to swap from a library design to a language sumtype.
  2. Allow value type exceptions to be baked by this, and to offer catch all.
  3. Easy for non-D programmers to learn and utilize this as a feature without changing their mental model. As a concept this has changed very little over the last 50 years, we do not need to invent a new set of design parameters.
sumtype S = :None | int;
sumtype S2 = int | bool;

S1 s1;
S2 s2 = s1.match {
	(:None) {
		if (random() > 0.5)
			return true;
		else
			return 2;
	};

	(int v) {
		return v;
	};
};

To access an element value in @system code use casting. Assigning is @safe.

sumtype S = int i | bool;
S s;

cast(:i)s = 2;
assert(cast(:i)s == 2);

Why is this member of operator necessary? What advantages does it have compared to matching based on type, like the current library sumtypes do? How is the following:

alias None = typeof(null);
sumtype S  = None | int;
sumtype S2 = int  | bool;

S1 s1;
S2 s2 = s1.match {
  (None) {
    if (random() > 0.5)
      return true;
    else
      return 2;
  };

  (int v) {
    return v;
  };
};

Improved by the member of operator?

5 days ago
On 13/09/2024 4:55 AM, Meta wrote:
> Why is this member of operator necessary? What advantages does it have compared to matching based on type, like the current library sumtypes do? How is the following:
> ```D
> alias None = typeof(null);
> sumtype S  = None | int;
> sumtype S2 = int  | bool;
> 
> S1 s1;
> S2 s2 = s1.match {
>    (None) {
>      if (random() > 0.5)
>        return true;
>      else
>        return 2;
>    };
> 
>    (int v) {
>      return v;
>    };
> };
> ```
> 
> Improved by the member of operator?

As a proposal, it is the only one of the three that does not require a name to be provided. Both Walter and Paul require a name for each element.

The benefit of _optionally_ supporting names, is that when the type is not unique in of itself, you can make it unique by adding a name.

Consider a web form, where you would want to take an email, where it could be for personal use or business use:

```d
sumtype WebFormEmail = :DoNotContact | string personal | string business;
```

The extra name is now unique between the two, and you can have a way of saying do not contact me, just show me the white paper.

As for your approach to aliasing null to create a non-unique type, there is only so many sentinels in the language you can do that for and the type of a null is itself a valid type to be in a sumtype. The closest thing to what you are doing there is ``void`` and there is only one of them.

The type of a member of operator has a size zero, this allows eliding of the value if all of them are size zero. The type of a null is the same as a pointer, after all its a pointer.

Being able to elide the value, to get a sumtype down to the size of just its tag is VERY important for error handling. See zero-cost exceptions proposal for C++ to see why I consider us needing that.

https://open-std.org/JTC1/SC22/WG21/docs/papers/2018/p0709r0.pdf

5 days ago

On Thursday, 12 September 2024 at 11:28:56 UTC, Richard (Rikki) Andrew Cattermole wrote:

>

The layout of the sumtype is variable size, allowing for copy constructors, destructors and no value (if size zero).

So this would enable variable-sized stack return? Do you have any plans for how that might be implemented?
Also this might be the only way to create a zero-sized type! (Hurray!)

>
  1. Allow value type exceptions to be baked by this, and to offer catch all.

;)

Also how costly do you think this version of sum types will be for compile times?

5 days ago
On 13/09/2024 10:25 PM, IchorDev wrote:
> On Thursday, 12 September 2024 at 11:28:56 UTC, Richard (Rikki) Andrew Cattermole wrote:
>> The layout of the sumtype is variable size, allowing for copy constructors, destructors and no value (if size zero).
> 
> So this would enable variable-sized stack return? Do you have any plans for how that might be implemented?
> Also this might be the only way to create a zero-sized type! (Hurray!)

It is not variable sized in that sense.

A specific sumtype would be fixed at compile time based upon the constraint set. The value you get from ``.sizeof`` is the size in assembly.

The variable size layout enables eliding aspects of it like destructors which are not used by a given constraint set.

> Also how costly do you think this version of sum types will be for compile times?

Each of the three designs require a declaration to define a sumtype.

There is no inline declarations, so I expect that all three will be at the same cost.

Mostly dependent upon the set operations.

So as long as the constraint set is kept small, shouldn't be noticeable I would think.

5 days ago

On Friday, 13 September 2024 at 09:26:16 UTC, Richard (Rikki) Andrew Cattermole wrote:

>

As for your approach to aliasing null to create a non-unique type, there is only so many sentinels in the language you can do that for and the type of a null is itself a valid type to be in a sumtype. The closest thing to what you are doing there is void and there is only one of them.

Just to demonstrate this, if void variables were allowed:

void a, b;
b = a; // OK
:X x;
:Y y = x; // error, can't implicitly convert :X to :Y
>

The type of a member of operator has a size zero, this allows eliding of the value if all of them are size zero. The type of a null is the same as a pointer, after all its a pointer.

That's great. Otherwise you would have to define empty struct types and they would probably waste one byte of space:

struct A {}
struct B {}
struct C {}

sumtype S = A | B | C;

A a; // size 1
:X x; // size 0
5 days ago
On 13/09/2024 11:16 PM, Nick Treleaven wrote:
> On Friday, 13 September 2024 at 09:26:16 UTC, Richard (Rikki) Andrew Cattermole wrote:
>> The type of a member of operator has a size zero, this allows eliding of the value if all of them are size zero. The type of a null is the same as a pointer, after all its a pointer.
> 
> That's great. Otherwise you would have to define empty struct types and they would probably waste one byte of space:
> 
> ```d
> struct A {}
> struct B {}
> struct C {}
> 
> sumtype S = A | B | C;
> 
> A a; // size 1
> :X x; // size 0
> ```

I did this specifically for value type exceptions:

```d
void func() @throws(:FailToDecodeUTF);
```

Would actually be:

```d
size_t func();
```

It just so happens, that this is also a useful codegen trait for sumtypes.

If you rely on structs, you would have to solve the size zero problem and that isn't pretty.

https://github.com/rikkimax/DIPs/blob/2a80adb38ac94dd38eac61505fc3c3810b9eae10/DIPs/DIP1xxx-RC.md