Champion "Discriminated Unions" #8926
Replies: 892 comments 18 replies
-
I wouldn't expect progress on this feature to precede records. |
Beta Was this translation helpful? Give feedback.
-
I've started writing a draft proposal around #75. Would you like me to continue with this, or did you have a proposal planned yourself? |
Beta Was this translation helpful? Give feedback.
-
@DavidArno I do not expect to invest any significant effort on this until we make progress on records. |
Beta Was this translation helpful? Give feedback.
-
OK, that's good to know. I'll carry on with my proposal then, but will take time over it as there's no rush. |
Beta Was this translation helpful? Give feedback.
-
/cc @agocke |
Beta Was this translation helpful? Give feedback.
-
I will be moving my proposal of "closed" type hierarchies from Roslyn to this repo shortly. I also think we should explore "real" discriminated unions and have some thoughts on that, but it's still much more of an exploratory phase for me. However, I think I'm close to a reasonably complete proposal for |
Beta Was this translation helpful? Give feedback.
-
One question that I think we need to ask (and I haven't seen anyone ask elsewhere) is whether the case classes can be used as types. Allow me to illustrate with the example of the Option type: public enum class Option<T>
{
None,
Some(T Value)
} Obviously public void MyMethod(Some<string> someString) // Is this allowed? It doesn't make much sense
{
// ...
} I think of ADTs as functioning more like enums, however they're actually implemented. So using each case as a type doesn't make sense, any more than this makes sense: public enum Colours
{
Red, Green, Blue
}
public void MyMethod(Blue colour)
{
// ...
} |
Beta Was this translation helpful? Give feedback.
-
I think it shouldn't be the case with class SyntaxNode {
case class Statement { } // implicitly inherit from SyntaxNode, as in, a "case" of the said type
case class Expression {
case enum class Literal { Numeric, String }
}
} |
Beta Was this translation helpful? Give feedback.
-
I think that feature can be added fairly simply using a custom type, in the same way I maintain a library, OneOf, which adds a The Example of using a OneOf as a return value:
example of Matching
As new types are added to the OneOf definition, compiler errors are generated wherever the union is This can be included in the BCL without language changes, although I'm sure some syntactical sugar could be sprinkled. this proposal was originally made at dotnet/roslyn#14208 and at #1524 . Sorry! |
Beta Was this translation helpful? Give feedback.
-
@mcintyre321 Your type Choice<'a, 'b> = Choice1Of2 of 'a | Choice2Of2 of 'b |
Beta Was this translation helpful? Give feedback.
-
While your library does accomplish providing a single discriminated union it also demonstrates the degree of boilerplate required to do so which is what this proposal seeks to reduce. Your types also don't work with C#'s recursive pattern matching which will make it much more efficient and much more capable to match over such a type: var backgroundColor = ...;
// no delegate invocation required
Color c = backgroundColor switch {
string str => CssHelper.GetColorFromString(str),
ColorName name => new Color(name),
Color col => col
}; |
Beta Was this translation helpful? Give feedback.
-
@Richiban OneOf<T0, ..., TN> has up up to 33 parameters, so is more useful as a general return object than Either or Choice. @HaloFour I agree it would be good to have |
Beta Was this translation helpful? Give feedback.
-
public enum class OneOf<T1, T2>
{
First(T1 value),
Second(T2 value)
} vs. this* * Yes, I know that you have all of the arities crammed into one, but the file is too large to link to a specific line. |
Beta Was this translation helpful? Give feedback.
-
@mcintyre321 I don't doubt it's usefulness (or the fact that it's better than My point was that discriminated unions are a much more general tool that can also solve the problem that I'm not sure how you would propose to implement the equivalent of this using an type FavouriteColour =
| Red
| Green
| Blue
| Other of string |
Beta Was this translation helpful? Give feedback.
-
@Richiban The abilities to naming a DU is useful, but you still get a lot of value with anonymous DUs. Is it a show-stopper to not have named unions (initially at least)? That said, there are some things that can be done. A Another alternative for naming is to use a Record type e.g. And you can always use an alias: I appreciate none of this is quite as nice as the F# approach, but perhaps the language could be extended to fix some of this. E.g. defining a union TBH I'm happy with any solution where
@HaloFour cramming it into one file is along the lines of https://referencesource.microsoft.com/#mscorlib/system/tuple.cs , although I admit OneOf.cs has become somewhat larger! *There's a class-based OneOfBase in the library, but the name isn't great IMO. |
Beta Was this translation helpful? Give feedback.
-
I made a proposal in the F# GitHub about a possible way to implement overlapped struct unions given the current runtime limitations fsharp/fslang-suggestions#1333 |
Beta Was this translation helpful? Give feedback.
-
FWIW any overlapping is probably trading size for throughput. Having a fixed-address offset, as opposed to a dynamic dispatch, will likely speed up the instruction pipeline. |
Beta Was this translation helpful? Give feedback.
-
I'll also say that I don't think I think Option is a good candidate for dedicated behavior, both because of it's particular characteristics and the prominence of the use case. Once we think about special-casing Option, the rest of the overlap use cases seem much less important to me. |
Beta Was this translation helpful? Give feedback.
-
How would that work if |
Beta Was this translation helpful? Give feedback.
-
I'm sure this has come up before, but sometimes |
Beta Was this translation helpful? Give feedback.
-
The niche optimization that Rust performs on its enums is what lets it treat an |
Beta Was this translation helpful? Give feedback.
-
As a point of clarification @TehPers, what you are calling "that kind of optimization in c#" would likely be better described as "that kind of optimization in .net". Layouts of objects, and special things around null-handling, are def in in the purview of the runtime vs the language. :) |
Beta Was this translation helpful? Give feedback.
-
In my proposal the address offsets are fixed at compile time and there's no dynamic dispatch. The tradeoffs are to work around the runtime limitations:
|
Beta Was this translation helpful? Give feedback.
-
This means that the type should have two representations: one for value types and nullable reference types, the other for non-nullable reference types. If only C# had definitely non-nullable types... |
Beta Was this translation helpful? Give feedback.
-
By special-casing, I mean special-casing in the runtime, similar to |
Beta Was this translation helpful? Give feedback.
-
would be great if for Option and Result existing FSharp.Core could be used or shared between roslyn and C#? to mantain some level of compatibility and also many parts of these are just already in place in F#? *FSharp.Core: Result why can't C#/Roslyn use FSharp.Core for this feature to some extent? this would allow all existing F# Result and Option types from libraries to be consumable from C# as well, plus, would make future C# result/options consumable from F# i am sure most likely there is reasons, but just double checking/curious |
Beta Was this translation helpful? Give feedback.
-
F# employs a lot of custom metadata from F# specific assemblies, plus a binary resource blob of additional metadata. I would expect that as a part of this proposal that the C# team will come up with language-agnostic metadata (e.g. attributes defined in BCL assemblies) and convention, and that the F# team could then hopefully adopt that metadata to enable those types to interoperate. |
Beta Was this translation helpful? Give feedback.
-
The latest proposal for this dropped a few days ago: https://github.com/dotnet/csharplang/blob/main/proposals/nominal-type-unions.md Some initial thoughts:
|
Beta Was this translation helpful? Give feedback.
-
The doc has this example: public union Option<TValue>(Some<TValue>, None)
{
public record struct Some<TValue>(TValue Value);
public record struct None();
} I could be wrong, but if the public union Option<TValue>(Some<TValue>, None)
{
public record struct Some(TValue Value);
public record struct None();
} |
Beta Was this translation helpful? Give feedback.
-
There is a discussion started for this new proposal here: |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
See
Design meetings
https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-08-31.md#discriminated-unions
https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-09-26.md#discriminated-unions
https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-07-24.md#discriminated-unions
Beta Was this translation helpful? Give feedback.
All reactions