As a long time C# dev, I feel like I’m missing something about this proposal. The use case for this doesn’t seem well defined to me. Could someone give me a real world example?
I’m pretty sure you can implement the example in this proposal by declaring an empty interface and having some record classes that “implement” it. Does that lose something that I’m not seeing?
That type of hierarchy is “open” in the sense that when you handle them you can’t know whether you handled all of them. Anyone can make a new implementation of your interface, potentially in code that isn’t yours.
Also, the options may not share any surface area at all, so the “interface” for all the types may be empty which is a bit unnatural in OO.
But under the hood, it’s just this: a type hierarchy. But the hierarchy is closed for extension, and (this is the key part you’d not get if doing this “manually” as you describe) when code uses the cases, failing to account for a case will cause an error at compile time.
I think the most down to earth example is the AST or data protocol examples. Take Jason for example. Instead of having a JsonValue class to hold the data you would have a Union type that is either string, bool, number, array of your union type or map type which uses a string as key and the same union type as value.
It would also allow to implement result types like rust has them. So you could define an API that returns a value or an error. I didn’t see in the proposal if they talked about generics though.
In functional programming world this is mainly known as algebraic data types. I can say that if you get used to model types like this you really start to miss it in languages that don’t support it.
This[0] lib implements F#-style discriminated unions. The way I've used it is to make my outer layers return "Result" types that wrap the DTO, error, etc. under one umbrella. It allows for pattern matching your results which makes error handling easier and also allows your models to evolve over time without changing your function signatures at the edge.
We have been using empty interfaces to simulate F# discriminated unions for the last 5+ years. It works like a charm. The problem "not all cases are handled in the switch" occurs very rarely (once per 50k lines of code) and, usually, we fix it after the first run of a smoke test. So, it's not a problem at all.
I think,.net team haven't added discriminited unions yet because it can be effectively simulated with interfaces.
A simple example is the Result<T> which could have either Ok(T value) or Error(string message). In order to get the value you need to switch over both cases, forcing you to handle the error case locally.
I’m pretty sure you can implement the example in this proposal by declaring an empty interface and having some record classes that “implement” it. Does that lose something that I’m not seeing?