Yes, “union” are not necessarily disjoint. See the not-very-good wikipedia page where it is called “tagged union”. Basically an enumeration where each case can carry (typed) parameters, not just a name.
If you don’t know of this feature, you are missing a fundamental concept about programming, which should be as primitive and regularly-used as pairs/tuples, but isn’t well-known because many programming languages ignore it completely, which is a shame. You should have a look!
Yep, dynamic languages don’t have the syntax for it, but the use case of course exists, e.g. discriminating with a “kind” or “type” field. Thanks for the pointer though.
Not-directly-related note: Some languages argue that because they already have constructs that enable dispatch-on-case, typically Go interfaces, they “don’t need tagged unions” which are a strict subset of their feature. I understand the argument (in the good cases, the dispatching constructs allow an encoding of typical pattern-matching that is tolerable, although it’s easy to overlook nested matching and other niceties). But I think it misses the point that tagged unions enables static exhaustiveness checking (does this switch/match cover all cases?), which is a huge win usability-wide: when I add a new case, the checker walks me through all the definitions that need to be updated. (Just like adding a new method in a class.) Scala realized this early, encoding algebraic datatypes as sealed classes. (Of course, this argument is moot for languages whose users are averse to static checking of their code.)
I am specifically discussing “disjoint sums” or “tagged unions”, which is not what most of the conversation in the references you gave is about (they spend most of their time discussing difficulties with non-disjoint sums, non-tagged unions, which are well-known to be much harder to design right in theory and also in practice). The topmost reply in the Github issue, on the other hand, is exactly on point:
The past consensus has been that sum types do not add very much to interface types. Once you sort it all out, what you get in the end is an interface type where the compiler checks that you’ve filled in all the cases of a type switch. That’s a fairly small benefit for a new language change.
That sounds like a good summary of the current thinking of Go designers. And I think it’s completely wrong! Statically checking the exhaustiveness of case distinction adds massive value when it comes to writing codebases that are robust to changes/refactoring. This is exactly what my post above was discussing (the reasoning of “we already have open dispatch” and how it misses the strong benefits of exhaustivity checking).
First class Enums are exciting - I will not miss making arrays of constants, or constants that are arrays of constants, to fill in the gap Enums would have filled.
I can’t help but think the implementation is over-complicated. Do we really need enum types that have methods and static methods, and can implement interfaces? Many languages have enums without any of these features and this is more than enough.
I guess variants / disjoint-sum-types / algebraic datatypes will have to wait for PHP 81.0.
PHP has unions since 8.0: https://www.php.net/manual/en/language.types.declarations.php#language.types.declarations.union
(I realise that one cannot name such a union yet.)
Did you mean something different by “disjoint sum type”?
Yes, “union” are not necessarily disjoint. See the not-very-good wikipedia page where it is called “tagged union”. Basically an enumeration where each case can carry (typed) parameters, not just a name.
If you don’t know of this feature, you are missing a fundamental concept about programming, which should be as primitive and regularly-used as pairs/tuples, but isn’t well-known because many programming languages ignore it completely, which is a shame. You should have a look!
Yep, dynamic languages don’t have the syntax for it, but the use case of course exists, e.g. discriminating with a “kind” or “type” field. Thanks for the pointer though.
Looking more into it, PHP has an RFC for tagged unions: https://wiki.php.net/rfc/tagged_unions and it seems that there is a larger effort to bring more features for algebraic types to PHP: https://wiki.php.net/rfc/adts
Not-directly-related note: Some languages argue that because they already have constructs that enable dispatch-on-case, typically Go interfaces, they “don’t need tagged unions” which are a strict subset of their feature. I understand the argument (in the good cases, the dispatching constructs allow an encoding of typical pattern-matching that is tolerable, although it’s easy to overlook nested matching and other niceties). But I think it misses the point that tagged unions enables static exhaustiveness checking (does this switch/match cover all cases?), which is a huge win usability-wide: when I add a new case, the checker walks me through all the definitions that need to be updated. (Just like adding a new method in a class.) Scala realized this early, encoding algebraic datatypes as sealed classes. (Of course, this argument is moot for languages whose users are averse to static checking of their code.)
Russ Cox’s post and the GitHub issue show some of the difficulties with adding sum types to Go.
I am specifically discussing “disjoint sums” or “tagged unions”, which is not what most of the conversation in the references you gave is about (they spend most of their time discussing difficulties with non-disjoint sums, non-tagged unions, which are well-known to be much harder to design right in theory and also in practice). The topmost reply in the Github issue, on the other hand, is exactly on point:
That sounds like a good summary of the current thinking of Go designers. And I think it’s completely wrong! Statically checking the exhaustiveness of case distinction adds massive value when it comes to writing codebases that are robust to changes/refactoring. This is exactly what my post above was discussing (the reasoning of “we already have open dispatch” and how it misses the strong benefits of exhaustivity checking).
First class Enums are exciting - I will not miss making arrays of constants, or constants that are arrays of constants, to fill in the gap Enums would have filled.
I can’t help but think the implementation is over-complicated. Do we really need enum types that have methods and static methods, and can implement interfaces? Many languages have enums without any of these features and this is more than enough.
The same could be said of classes; why should structs have methods!