August 09, 2018
On Wednesday, 8 August 2018 at 20:54:13 UTC, Paul Backus wrote:
> SumType is a generic sum type for modern D. It is meant as an alternative to `std.variant.Algebraic`.

Version 0.5.2, with fixes for the bugs reported in this thread, is now available. Thanks to vit for their detailed feedback!

In order to avoid spamming the newsgroup, I'd like to encourage everyone to submit further bug reports, feature requests, etc. as Github issues at https://github.com/pbackus/sumtype/issues
August 10, 2018
On Thursday, 9 August 2018 at 15:56:12 UTC, Paul Backus wrote:
> On Wednesday, 8 August 2018 at 20:54:13 UTC, Paul Backus wrote:
>> SumType is a generic sum type for modern D. It is meant as an alternative to `std.variant.Algebraic`.
>
> Version 0.5.2, with fixes for the bugs reported in this thread, is now available. Thanks to vit for their detailed feedback!
>
> In order to avoid spamming the newsgroup, I'd like to encourage everyone to submit further bug reports, feature requests, etc. as Github issues at https://github.com/pbackus/sumtype/issues

It would be nice if some actual examples could be given. The help on dub is a bit confusing because the the code is not complete.
August 10, 2018
On Friday, 10 August 2018 at 12:35:18 UTC, Everlast wrote:
> On Thursday, 9 August 2018 at 15:56:12 UTC, Paul Backus wrote:
>> On Wednesday, 8 August 2018 at 20:54:13 UTC, Paul Backus wrote:
>>> SumType is a generic sum type for modern D. It is meant as an alternative to `std.variant.Algebraic`.
>>
>> Version 0.5.2, with fixes for the bugs reported in this thread, is now available. Thanks to vit for their detailed feedback!
>>
>> In order to avoid spamming the newsgroup, I'd like to encourage everyone to submit further bug reports, feature requests, etc. as Github issues at https://github.com/pbackus/sumtype/issues
>
> It would be nice if some actual examples could be given. The help on dub is a bit confusing because the the code is not complete.

Also, I'm curious how one can handle a collection of types with match?

Suppose I have SumType!(int, float, string)

and I wanted a generic match on int and float. Something like

(int | float _) => would be awesome, but that is invalid.

Of course, there are work arounds but the goal is for simplification in a canonical way.

One way would be to be able to call other handlers directly:

(int _) => { return match.float(_); }
(float _) => { ... }

which, say, calls the float delegate. This is just "chaining" but is a nice universe syntax and is good if it can be implement(with inlining occurring).

Another way would be to allow for "sub-SumType'ing":

alias X = SumType!(int, float, string);

(X.SubType!(int, float) _) { ... }

or whatever, again, a few ways one can go about this.

Also, a full algebra would be nice ;)

alias X = SumType!(int, float, string)
alias Y = SumType!(complex, vector)

alias Z = SumType.Union(X,Y);

Z is a "super type" as could have been expressed as

alias Z = SumType!(int, float, string, complex, vector).

except, of course, Z would be typed in terms of X and Y.

The difference is that Z is compatible with X and Y. But this might require a little work because something like Z.X is not the same as X due to the fact that X's typeinfo(an index value?) cannot represent Z(it would be nice if it could but I think it might be fragile to do so unless hashing solves the problem).

Then intersection can also be defined:

SumType.Intersect(X,Y) = SumType!(Null) = null;

But if Y had a string then

SumType.Intersect(X,Y) = SumType!(string); (which should reduce to string)

But the problem is that, a string in X and a string in Y may have no relationship programmatically(one may encode a series of bits for, say, compression and the other an error string). This then requires some way to know which type is being acted on(X or Y) as so the in sub-types can be properly interpreted.


If one notices, this is similar to inheritance in that union is related to derivation and intersection to reduction. The notions, if they can be consistently defined, allows one to build type structures that are hierarchically based and parallel classes. In fact, classes could be seen as a product of SumTypes on single elements:

alias C = ProdType!(SumType!string, SumType!int);

C then would act like a class with a string and int field with the functionality of both combined and

class Q;
alias D = ProdType!(SumType!float, C, Q);

would be a derivation from C and Q and adding a float member.

Of course, this would be a lot of work and require mathematical consistency and not offer much over the current method(in which ultimately it would be equivalent to I believe) but it does provide completeness. Of course, I suppose if we were to go down that path we'ed then require a full on algebraic system so we could do stuff like exponentiation, etc ;)












August 10, 2018
On Friday, 10 August 2018 at 13:11:13 UTC, Everlast wrote:
> On Friday, 10 August 2018 at 12:35:18 UTC, Everlast wrote:
>>
>> It would be nice if some actual examples could be given. The help on dub is a bit confusing because the the code is not complete.

In addition to the example on the dub page, there are a some in the API docs at https://pbackus.github.io/sumtype/sumtype.html that go into more detail.

If by "not complete" you mean that they lack a `main` function, that's because they're defined as `unittest` blocks in the source. This ensures that they are always correct and up-to-date with the latest version of sumtype. I hope you will agree that having to type `void main()` and a couple braces is an acceptable price to pay for such quality assurance. :)

> Also, I'm curious how one can handle a collection of types with match?
>
> Suppose I have SumType!(int, float, string)
>
> and I wanted a generic match on int and float. Something like
>
> (int | float _) => would be awesome, but that is invalid.
>
> [...]

You can do this with a template handler that introspects on the type of its argument:

(num) {
    alias T = typeof(num);
    static assert(is(T == int) || is(T == float));
    // code to handle int or float goes here
}

If you want nicer syntax, you can factor out the type assertions into a template wrapper:

SumType!(int, float, string) x;
x.match!(
    acceptOnly!(int, float,
    	num => writeln("number")
    ),
    (string _) => writeln("string")
);

Full code here: https://run.dlang.io/is/MrzF5n

> Also, a full algebra would be nice ;)
>
> alias X = SumType!(int, float, string)
> alias Y = SumType!(complex, vector)
>
> alias Z = SumType.Union(X,Y);
>
> Z is a "super type" as could have been expressed as
>
> alias Z = SumType!(int, float, string, complex, vector).

You can do this already with `alias Z = SumType!(NoDuplicates!(X.Types, Y.Types));`, using `std.meta.NoDuplicates`. I don't know of an equivalent template for getting the intersection of two type sequences, but you could probably cobble something together with `std.meta.Filter`.

> except, of course, Z would be typed in terms of X and Y.
>
> [...]

What you are describing here is, essentially, an entirely new type system. It's interesting as a thought experiment, but ultimately, D already has a type system, and I would much rather have SumType work with the existing system than invent its own.

(Also, what you call `ProdType` already exists. It's called `Tuple`, and is located in the module `std.typecons`.)
August 10, 2018
On Friday, 10 August 2018 at 19:19:39 UTC, Paul Backus wrote:
> On Friday, 10 August 2018 at 13:11:13 UTC, Everlast wrote:
>> On Friday, 10 August 2018 at 12:35:18 UTC, Everlast wrote:
>>>
>>> It would be nice if some actual examples could be given. The help on dub is a bit confusing because the the code is not complete.
>
> In addition to the example on the dub page, there are a some in the API docs at https://pbackus.github.io/sumtype/sumtype.html that go into more detail.
>
> If by "not complete" you mean that they lack a `main` function, that's because they're defined as `unittest` blocks in the source. This ensures that they are always correct and up-to-date with the latest version of sumtype. I hope you will agree that having to type `void main()` and a couple braces is an acceptable price to pay for such quality assurance. :)

No, I was thinking of the dub page. Is saw the unit tests which were better.

>
>> Also, I'm curious how one can handle a collection of types with match?
>>
>> Suppose I have SumType!(int, float, string)
>>
>> and I wanted a generic match on int and float. Something like
>>
>> (int | float _) => would be awesome, but that is invalid.
>>
>> [...]
>
> You can do this with a template handler that introspects on the type of its argument:
>
> (num) {
>     alias T = typeof(num);
>     static assert(is(T == int) || is(T == float));
>     // code to handle int or float goes here
> }
>
> If you want nicer syntax, you can factor out the type assertions into a template wrapper:
>
> SumType!(int, float, string) x;
> x.match!(
>     acceptOnly!(int, float,
>     	num => writeln("number")
>     ),
>     (string _) => writeln("string")
> );
>
> Full code here: https://run.dlang.io/is/MrzF5n
>

Ok, thanks!

>> Also, a full algebra would be nice ;)
>>
>> alias X = SumType!(int, float, string)
>> alias Y = SumType!(complex, vector)
>>
>> alias Z = SumType.Union(X,Y);
>>
>> Z is a "super type" as could have been expressed as
>>
>> alias Z = SumType!(int, float, string, complex, vector).
>
> You can do this already with `alias Z = SumType!(NoDuplicates!(X.Types, Y.Types));`, using `std.meta.NoDuplicates`. I don't know of an equivalent template for getting the intersection of two type sequences, but you could probably cobble something together with `std.meta.Filter`.
>

Yes, but

>> alias Z = SumType.Union(X,Y);

is not the same as

>> alias Z = SumType!(int, float, string, complex, vector).

In the first case Z is actually a union of 2 types while in the second it is of 5. There is a subtle difference in that in the second case the types lose relation. E.g., there is no way to recover X or Y from Z but in the first we can:

We can see this explicitly:

union X
{
   int;
   float;
   string;
}


union Y
{
   complex;
   vector;
}

union Z
{
   X;
   Y;
}

union ZZ
{
   int;
   float;
   string;
   complex;
   vector;
}


ZZ is flat while Z is hierarchical.

I'm not sure how SumType deals with type info, if it is local or global. If it were global, then Z would definitely be different than ZZ.

>> except, of course, Z would be typed in terms of X and Y.
>>
>> [...]
>
> What you are describing here is, essentially, an entirely new type system. It's interesting as a thought experiment, but ultimately, D already has a type system, and I would much rather have SumType work with the existing system than invent its own.

It's not entirely different but a different representation. Ultimately it should be isomorphic.

> (Also, what you call `ProdType` already exists. It's called `Tuple`, and is located in the module `std.typecons`.)


Yes, Tuple is a product over types, but we are talking about in the context of including type info for matching and such which tuples don't directly have.

What I'm ultimately talking about is to allow one to compare these types, to match, etc in a way that is more sophisticated than having to match directly on the types.

E.g., what if we wanted to match on "inheritance"? How can that be done?

Using the Z above, We could write a match on X and or Y. This is more direct than using ZZ, although we could do somewhat just as easy. But suppose we would like to match for anything that uses X?

Z, which uses X, acts very similar to a derived class and this info can be used to provide more appropriate matching.


D already has a great type system with it's many advanced features but these are pretty much static while the point of sum types is to provide dynamic resolution. Maybe some combination could be used. Since SumType is already a D type it can use the D's typing features but since SumType is effectively sealed in this sense it doesn't work too well.

e.g.,

alias Z = SumType!(X,Y) is a type itself and effectively inherits from X and Y but this relationship is not expressed in any meaningful way in SumType.

Maybe SumType!(X,Y) could return a new type that is a class that inherits from X and Y? (unfortunately this can't work because of single inheritance but these types could probably be wrapped in interfaces and properties could be used)

Then, say

alias Z = SumType!(X,Y)

is a new type which has all the characteristics of a SumType but also can work in the ecosystem of D's type system too.

For example, it would be nice to have relations such as

SumType!(X,Y) : SumType!(X)

or

cast(X)SumType!(X,Y) == X

etc.

This might be require quite a bit more work and I'm not sure if it all would work out well or not but if it did it would leverage quite a bit of power.








August 11, 2018
On Friday, 10 August 2018 at 21:28:40 UTC, Everlast wrote:
> Yes, but
>
>>> alias Z = SumType.Union(X,Y);
>
> is not the same as
>
>>> alias Z = SumType!(int, float, string, complex, vector).
>
> In the first case Z is actually a union of 2 types while in the second it is of 5. There is a subtle difference in that in the second case the types lose relation. E.g., there is no way to recover X or Y from Z but in the first we can:
>
> [...]
>
> ZZ is flat while Z is hierarchical.

If you want to nest SumTypes, there's nothing stopping you from doing that already. `alias Z = SumType!(X, Y);` will work just fine. I wouldn't necessarily recommend it, since each layer introduces additional memory overhead to store the type tag, but it will work.

> I'm not sure how SumType deals with type info, if it is local or global. If it were global, then Z would definitely be different than ZZ.

I assume by type info, you mean the tag values? If so, they're local to each instantiation of SumType. For example, in SumType!(A, B), a tag value of `0` corresponds to the type `A`, whereas in SumType!(C, D), the same tag value of `0` corresponds to the type `C`.

> alias Z = SumType!(X,Y) is a type itself and effectively inherits from X and Y but this relationship is not expressed in any meaningful way in SumType.

I think we may each have a slightly different idea of what "inheritance" means, in this context. When you say "Z inherits from X and Y", I interpret that as meaning "Z is a subtype of X and Y"--which isn't true. A subtype should obey the substitutability principle: anywhere I can use an object of the supertype, I should be able to substitute an object of the subtype, and my program should still be correct when I do.

But suppose I have a function that accepts an X, and I pass it a Z instead. What happens if it turns out, at runtime, that the Z contains a Y? The function won't be prepared to handle it--in other words, my program is now incorrect. That means the substitution was invalid, and Z is not a subtype of X after all.

Now, the relationship *does* hold in the opposite direction: if I have a function that accepts a Z, I can pass it an X, and it will be able to handle it. Granted, I'll have to do a little bit of extra typing to wrap the X (i.e., `f(Z(x))` rather than just `f(x)`), because D doesn't allow user-defined implicit conversions, but that's not a terribly large burden. (And if I really want to write `f(x)`, I can always define an overload of `f` that takes an `X` argument and forwards to the `Z` version.)

> Maybe SumType!(X,Y) could return a new type that is a class that inherits from X and Y? (unfortunately this can't work because of single inheritance but these types could probably be wrapped in interfaces and properties could be used)

It sounds like I may not have been clear enough about what SumType's goal actually is. SumType's goal is not, and has never been, to be a feature-complete alternative to OOP language features like classes, interfaces, and inheritance. Those features already exist in D; there's no need to duplicate them.

SumType's goal is, instead, to provide a much more limited, much less flexible version of polymorphism--one that requires every possible "subclass" (i.e., member type) and every "method override" (i.e., match handler) to be fixed up-front, in one place, at compile time. You cannot add new member types to an existing SumType, the way you can add new derived classes to an existing base class--and this is by design!

Why is SumType designed this way? Because it turns out that this very limited version of polymorphism can be implemented *much* more efficiently than the real thing. It doesn't require virtual methods, it doesn't require heap allocation, and it doesn't require runtime type information. In fact, the only thing keeping SumType from being BetterC compatible right now is the fact that it uses DRuntime features *during CTFE*!

Anyway, I hope I've explained things clearly enough now to avoid further misunderstanding. Please let me know if you have any more questions or ideas--I really appreciate the thought you've put into this discussion.
August 22, 2018
On Wednesday, 8 August 2018 at 20:54:13 UTC, Paul Backus wrote:
> SumType is a generic sum type for modern D. It is meant as an alternative to `std.variant.Algebraic`.

New point release, 0.5.3, with the following updates:
- SumType now uses the smallest possible integer type for its tag (e.g., `ubyte` if the number of types is less than 255).
- A bug involving structs with invalid .init values has been fixed.
1 2
Next ›   Last »