Nominal Type Unions for C# Proposal by the C# Unions Working Group
Posted by DayYam@reddit | programming | View on Reddit | 6 comments
Posted by DayYam@reddit | programming | View on Reddit | 6 comments
AyrA_ch@reddit
I don't think the benefits of this proposal outweigh the extra complexity caused by the additional level of indirection.
How often do you actually need to handle a closed set of multiple completely independent types in a single function?
I would only ever find this useful if it automatically exported properties and functions that have an identical signature in all specified types. This would allow me to pretend that types I have no declarative control over implement some kind of interface automatically without having to write a manual wrapper class, but as it stands now, this doesn't provides a significant benefit over declaring something as object and then type check where needed.
Slsyyy@reddit
> How often do you actually need to handle a closed set of multiple completely independent types in a single function?
In OOP not so often, because people are used to model the code around subtype polymorphism. Both approaches are good, they are just different tradeoffs:
* in subtype polymorphism it is easy to add a new implementation (just a new class implementing a given interface), but it is hard to add a new method (you need to implement it everywhere and with bad design those methods may not make sense at all for all implementations)
* in union type it is hard to add new implementation (you need to add another `case` in every `switch` statement based on the union type), but it is easy to add new method (just just modify that single switch statement in a place, where it is needed)
Guys like Uncle Bob are manipulating, that subtype polymorphism is all you need by cherry-picking the examples, which works great in that case. My opinion is that subtype polymorphism is more dangerous option: work really well with good design, but sucks in a bad one. Unions are more balanced and easier to to design.
Imagine writing a chess engine. A good OOP design is really hard. In contrast an Union type of each type of piece (King, Queen etc) it is much easier as union type better represent the real world than class design. With that Union type you can just add `switch` cases in multiple places like:
* AI evaluation
* Move restrictions
* Serialization
* Display
* Board setup
where subtype polymorphism really sucks at due to SRP. You cannot simply have a `Pawn` class, which is do all logic related to `Pawn` in all in-game code
AyrA_ch@reddit
In those cases you use a base class, not an interface. This is the reason why there is an abstract System.IO.Stream class. Very seldom do all properties make sense for all types of streams, so the base class implements them in a defensife manner, and you can override those you need. This also means adding a new method to a base class doesn't necessitates updating every implementation of it unless you mark the method as abstract. C# even allows you to provide default implementations in interface now (which I personally think is super ugly), which solves the limit of only being able to derive from at most one base class.
And that exactly is ugly. If you add a new type to your union you have to check every single function that touches this type to make sure it handles the new type. Static analysis can't do that for you because the static analyzer will never know whether you are purposefully not intending to handle the new type, or if it was by accident. Best you can do is to make your implementation throw exceptions on unknown types, but this turned something that should be a static code analysis task into a runtime error. Using OOP derivation practices forces me to implement new types in the appropriate locations. You listed 5 possible components where you need this switch statement, and I guarantee you, you will not only need this once per component, you need it to some extent in every function that handles pieces. Chess has 6 distict piece types, and by needing more switch statements than there are pieces you create unnecessary work for you and technical debt for the next person. It would be better to offload the implementation to the pieces instead. If I'm somehow worried that updating an interface forces me to provide an implementation to countless derivatives, I can make them derive from a base class that implements the basic functions (like
IsPathObstructed
) instead. Then I have to implement it exactly once, and all pieces can use it. The knight doesn't needs it, so I simply don't call that function inside fromGetPossibleMoves
, or I can override the implementation with a hardcoded "false", depending on what pattern I prefer.This is probably why these things don't exist in C#, but are present in F#.
Also pretty much everything they suggest in the documentation is accompanied by the equivalent C# code you would need to write now. In other words, we don't need to add this to the language at all, we can just put it into a nuget package with a source code generator.
Willing_Row_5581@reddit
Unions are one of the essential patterns of programming when explained in mathematical terms.
Just like you don't need functions because after all you can emulate them with a go-to statement but at the same time our standard representation of functions is a solid one...
...because it mirrors the correct mathematical properties.
Same applies to unions.
Mathematics is part of programming, might as well take advantage of it.
PS: if you want some background look into BCCC in Category Theory, it is the quintessential definition of the expressive power of programming languages and their type systems.
AyrA_ch@reddit
What you explain can already be provided with the plethora of
OneOf<T,...>
types.Willing_Row_5581@reddit
Wait I thought we had had them for years
https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/discriminated-unions
/s
;)