Hacker News new | ask | show | jobs
by 98347598 74 days ago
It's very disappointing that they aren't supporting Rust-style discriminated unions.
5 comments

In C#, all instances have a class, so there is already a discriminant, the class itself.

In the article, the example with the switch works because it switches on the class of the instance.

Null doesn't. `union(Int?, String?)` will only have 1 type of null, unlike a proper discriminated union.
The C# unions as described are discriminated unions.

The fact that they flatten a union of optional types into an optional union of the corresponding non-optional types is indeed a weird feature, which I do not like, because I think that a union must preserve the structural hierarchy of the united types, e.g. a union of unions must be different from a union of all types included in the component unions, and the same for a union of optional types, where an optional type is equivalent with a union between the void/null type and the non-optional type, but this C# behavior still does not make the C# unions anything else but discriminated unions, even if with a peculiar feature.

> that a union must preserve the structural hierarchy of the united types, e.g. a union of unions must be different from a union of all types included in the component unions, and the same for a union of optional types, where an optional type is equivalent with a union between the void/null type and the non-optional type

This is exactly the difference between simple union types and discriminated unions. This c# feature is what typescript has, not what Haskell/java/f#, etc.

The word "discriminated" by itself does not specify this property.

"Discriminated" just means that at run time you can discriminate the values of a union type by their current type, so you can use them correctly in expressions that expect one of the component types.

I agree that the right implementation of discriminated types is that mentioned by you and which is that of many important programming languages, but even if I disapprove of this property that the C# unions have, which in my opinion may lead to unexpected behavior that can cause subtle bugs, the C# unions are still discriminated unions, where you can discriminate the current type at run-time, with a "switch".

In my opinion, one should avoid this weird behavior of C#, by always defining only unions of non-optional types. Where needed, one should then define an optional type having as base a union type. Then these unions will behave like the discriminated unions of other languages.

Whether you use or not this policy, there are types that the C# unions cannot express, but if you use this policy, at least the limitations become explicit.

These are discriminated unions, even if they may be not Rust-style.

You can see in the examples, how "switch" uses the implicit discriminant of the union to select the code branch that must be executed, depending on the current type of the value stored in an union.

The syntax of the "switch" seems acceptable, without too many superfluous elements. It could have been made slightly better, by not needing dummy variables that must be used for selecting structure members (or used in whichever else expression is put in the right hand side; the repetition of the dummy variable names could have been avoided if the name of the union variable could have been reused with a different meaning within a "switch", i.e. as a variable of the selected type).

I do not see what else do you want. Perhaps Rust has reused the traditional term "discriminated unions", which had been used for many decades before Rust, and which means the same thing as the more recent terms "tagged unions" or "sum types", with a non-standard meaning and you have that in mind.

I don't think these are discriminated. From the docs:

> Union types — exhaustive matching over a closed set of types

> Closed hierarchies — exhaustive matching over a sealed class hierarchy

> Closed enums — exhaustive matching over a fixed set of enum values

I believe the last one would be sum types (disc. unions). This one allows overlapping types.

The word "discriminated" means that at run time, if you receive a value whose type is a union of types you can discriminate which is the actual type of the value, so you can use that value in expressions that expect a specific type, not a union of types.

This is in contrast with what is called "union" in the C language, where you must know a priori the type of a value in order to use it correctly.

Moreover, if you can discriminate at run time which is the actual type, that means that you can discriminate between values that happen to have the same representation, but which come from distinct types, e.g. if you had a union between "signed integer" and "unsigned integer", you could discriminate between a signed "3" and an unsigned "3".

This property ensures that the number of possible values for a discriminated union type is the sum of the numbers of possible values for the component types, hence the alternative name "sum type", unlike for a non-discriminated union, where the number of possible values is smaller, because from the sum you must subtract the number of possible values of the intersection sets.

The C# unions described in the article are discriminated unions, except that their component types are not the types literally listed in their definition. If some of the component types are optional, than the true union components are the corresponding non-optional types, together with the null a.k.a. void type, which I find as a rather strange choice.

Hi there! One of the C# language designers here, working on unions. We're extremely interested in discriminated unions. A real problem is that there so much interest, with many varying proposals on how best to do them. It's a lot to go through, and we've found some of the best designs layer on standard unions. So we like this ordering to lay the foundation for discriminated unions to built on top of! :)
One step at a time
ML-style discriminated unions, actually.