[LDM] - Type Unions Proposal #8313
Replies: 37 comments 220 replies
-
Don't see how this is possible. If the |
Beta Was this translation helpful? Give feedback.
-
For ad hoc unions, will stuff like |
Beta Was this translation helpful? Give feedback.
-
Are we going to be able to provide methods on the DU like all the other first class types in C#? Extension methods? How would this work for ad-hoc unions? |
Beta Was this translation helpful? Give feedback.
-
Defaults are fine if exhaustiveness is intrinsically guaranteed on the
check right? Maybe I'm not understanding your statement correctly.
…-J
On Thu, 25 July 2024, 15:54 Massimiliano Donini, ***@***.***> wrote:
Will a default be forbidden in a switch on unions? It would be nice not to
be allowed to opt out of exhaustivness checking with DU
—
Reply to this email directly, view it on GitHub
<#8313 (reply in thread)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAGKWZ6I3ZTPQQBWOQMX5MTZOCHH3AVCNFSM6AAAAABLM3DJRGVHI2DSMVQWIX3LMV43URDJONRXK43TNFXW4Q3PNVWWK3TUHMYTAMJUGQ4TGOA>
.
You are receiving this because you commented.Message ID:
***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
May I suggest that this would make union types not first class in that
sense...
…On Thu, 25 July 2024, 14:02 Matt Warren, ***@***.***> wrote:
That's yet to be determined. The current proposal does not allow for it.
—
Reply to this email directly, view it on GitHub
<#8313 (reply in thread)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAGKWZY52ZTAPBOMK2V4Q7LZOB2DVAVCNFSM6AAAAABLM3DJRGVHI2DSMVQWIX3LMV43URDJONRXK43TNFXW4Q3PNVWWK3TUHMYTAMJUGQ2DEMQ>
.
You are receiving this because you commented.Message ID:
***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
Fantastic proposal, unions would be a very welcome addition to the language! I have slight concerns around the proposed Now, where does a type like this really shine? If you're dealing with ref types, null is already a perfectly good "missing value" in almost all practical cases. Unfortunately, you're currently in trouble if you have an unconstrained generic parameter
This is, in my opinion, the big primary use case where Now, the slight issue with the current proposal: There's fairly measurable "unnecessary" overhead for ref types; I've tried something very similar for one of my projects, effectively implementing Option similar to In principle, if If that isn't feasible, I fear we might be facing one of two outcomes: |
Beta Was this translation helpful? Give feedback.
-
I think the proposal is pretty great overall :) , but I do have a few concerns / suggestions I think it would be great if we could specify an open union somehow, but still have all the benefits of the new union types (like the nice pattern matching syntax, and easy declaration, whilst still only allowing me to add new options later), for example, if I have one like this: union MyMathOperator
{
Add(int left, int right);
Subtract(int left, int right);
Multiply(int left, int right);
Divide(int left, int right);
Negate(int left);
} But I want to leave open the possibility of adding more cases later (like It would also be useful in some cases if we could specify the kind enum's type and/or values explicitly, for example: union struct WasmShortInstruction : byte
{
Unreachable = default; // or 0x00
Ref_Null(RefType inlineType) = 0xD0;
I32_Eq = 0x46;
//...
} The use of records concerns me a bit, as it doesn't seem like it would be possible to get a reference to the "fields" (is this what they're called for unions?). For example, it does not seem like it would be possible to write the following in any way (without completely abandoning all the benefits of unions): union struct MyLargeUnionStruct
{
Case1(InlineArray16<float> values);
Case2(InlineArray16<int> values, int controlValue);
Case3(InlineArray16<int> intValues, InlineArray16<float> floatValues);
Case4;
Case5(int value);
//...
}
int Handle(in MyLargeUnionStruct x) => x switch
{
Case1(ref readonly var values) => HandleCase1(in values),
Case2(ref readonly var values, var controlValue) => HandleCase2(in values, controlValue),
Case3(ref readonly var intValues, ref readonly var floatValues) => HandleCase3(in intValues, in floatValues),
Case4 => HandleCase4(),
Case5(var value) => HandleCase5(value),
//...
}; I also think it would be good if we document I also have some concerns about Thanks for the detailed proposal document though :) I feel much more optimistic about this feature actually happening and will work well (performance-wise) now overall |
Beta Was this translation helpful? Give feedback.
-
I don't see anything in the proposal about co- and contra-variance being added to classes and structs. While it's not required for unions (and certainly not for v1), I think it's going to seriously hurt the adoption of types such as |
Beta Was this translation helpful? Give feedback.
-
Just want to say: Thank you for putting the proposal together, I love it, looking forward to it! 🚀 |
Beta Was this translation helpful? Give feedback.
-
To add a use case: serialization. Many serialization formats support some kind of union type. E.g. Json, protobuf, msgpack, Bson in MongoDb. To serialize/deserialize unions in them, the typical solution in C# is to declare a base class with derived classes. A key gap is that deserializers need to know all the possible derived types, which must be specified again somehow, e.g. by annotating with attributes. This is similar to the exhaustiveness requirement. A built-in union type will definitely simply this. In case a union includes a primitive type option, the class heritance solution cannot be used. Well, we can still hack it by implementing custom serializer/deserializer. But that's a bit stretch for such a simple task. The ad hoc union in this proposal looks like a good fit for this scenario. |
Beta Was this translation helpful? Give feedback.
-
What about "common" data? union struct U(DateTime P)
{
A(DateTime P, int X, string Y) : base(P), // do we explicitly specify these parameters?
B(int Z), // or do we implicitly bring them "forward" to all options?
C,
D(_, TimeSpan Q) // or maybe some syntax to make it more readable that base parameters are "forwarded"?
}
U u = new A(DateTime.Now, 123, "abc");
Console.Write(u.P);
u = new B(DateTime.Now, 456); |
Beta Was this translation helpful? Give feedback.
-
For ad-hoc (anonymous) type unions, was any consideration given to optimizing them for struct-only options to avoid boxing, or it's too complicated? (int or DateTime) x = 123;
// compiled to something like this:
struct IntOrDateTime
{
int _tag;
[FieldOffset(4)]
public int Int;
[FieldOffset(4)]
public DateTime DateTime;
} PS. I thought about this more. For non generic types, it's doable (I think) - there are complications, but nothing insurmountable. The question is, what to do with generics if they're |
Beta Was this translation helpful? Give feedback.
-
Can I make a suggestion to keep these type proposals mathematically sound? A Records are basically named tuples with a syntax to create product types with named members. Adding methods to a named union type could be done analogously to how they are done with records. The more I think about it, a special syntax for ad-hoc unions leaves the language non-orthogonal from a mathematical point of view, because there is no special syntax for ad-hoc tuples (say with |
Beta Was this translation helpful? Give feedback.
-
Excellent proposal, but:
|
Beta Was this translation helpful? Give feedback.
-
Has it been considered to implement ad hoc unions with compiler generated classes (similar to display classes and other existing approaches) instead of using type erasure? What's the reasoning to go the erasure route here? It feels like this could limit potential use cases with reflection or generics in the future? |
Beta Was this translation helpful? Give feedback.
-
Taking erasure into consideration, I wonder if following would be possible: bool IsA<T>(object o) => o is T;
bool isIntOrString1 = IsA<int or string>(5); // expected true
bool isIntOrString2 = IsA<int or string>(DateTime.Now); // expected false |
Beta Was this translation helpful? Give feedback.
-
Given we're gonna have a Result<T,E> type, could we also have an error propagation operator? Experimenting with a Result<T,E> type that my team and I use on our project we've realized it can be very cumbersome evaluating many calls that return a Result type. We're often checking
Since
Of course this is pseudo-code as |
Beta Was this translation helpful? Give feedback.
-
This proposal generated some discussion on Hacker News, and many people's first response (including my own) was about how the term "union type" as a catch all term seems incorrect or confusing. Union Type is not a passable abbreviation for a Discriminated Union Type. "Sum type" would be a more commonly used term here. In contrast, what the proposal currently calls "Ad Hoc Union Types" really does seem to be a union of types, so here the term "union type" seems very appropriate. Would it be possible to amend the proposal with clearer terminology, reducing the amount of confusion? |
Beta Was this translation helpful? Give feedback.
-
Agree with Jared here...
I'm totally not in favour of special operators for any more types.
What would work nicely - but is a moon-shot at this point - is some
syntactic sugar for monadic bind, which would deal consistently with types
like Option (and Result, and Task, and List, and other types) used to
compose function invocations...LINQ is available but a bit clunky, and we
have already too many special operators in the language...
Other languages have simplified and normalized the approach to composing
functions that return these kinds of types, and they turn out to be quite
easy to learn and elegant to use...
…-John
On Fri, 9 Aug 2024 at 08:22, Jared Parsons ***@***.***> wrote:
Then maybe the main question here is: could we have an implicit return
operator that could be used by Result<T,E> for error propagation?
This is where I will keep pushing the "why is Result special?" C# is 20
years old, it has a variety of ways to represent *error* scenarios in
value the most common of which is null. Thus far we've gotten away
without having an operator that silently returns from a method when
encountering those values. What about Result makes it special that we
should doing this?
IMHO Result is not special enough to warrant this type of new operator. I
think our existing control flow mechanisms are fine for this. I do think we
should look at operators like ?? and adapt them for Result. But I don't
think it warrants an entirely new class of control flow.
—
Reply to this email directly, view it on GitHub
<#8313 (reply in thread)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAGKWZZSH5DOM6FAB55DKNLZQPVTFAVCNFSM6AAAAABLM3DJRGVHI2DSMVQWIX3LMV43URDJONRXK43TNFXW4Q3PNVWWK3TUHMYTAMRYGAZTGMI>
.
You are receiving this because you commented.Message ID:
***@***.***>
--
*John Azariah*
*m *+61 421 100 747
*e* ***@***.***
*t* @johnazariah <http://twitter.com/johnazariah>
[image: See the source image]
|
Beta Was this translation helpful? Give feedback.
-
Would it be possible to move the addition of an Option and/or Result type into a separate proposal and discussion? I feel this has nothing to do with the sum type syntax for two reasons
|
Beta Was this translation helpful? Give feedback.
-
Is there a reason why ad-hoc unions use a verbose syntax with parentheses:
instead of something like:
or
? I can see this getting longer in cases like: ((TextData or BinaryData) first, (Document or Database or Filesystem) second, (Person or Animal) third)
Combine(IEnumerable<((TextData or BinaryData), (Document or Database or Filesystem), (Person or Animal))> elements) {
} Instead of something like: (TextData + BinaryData first, Document + Database + Filesystem second, Person + Animal third)
Combine(IEnumerable<(TextData + BinaryData, Document + Database + Filesystem, Person + Animal)> elements) {
} |
Beta Was this translation helpful? Give feedback.
-
What is his memory layout like? If I create (TimeOnly or long), will I use hours and minutes as long? |
Beta Was this translation helpful? Give feedback.
-
If (A or B) cannot call a method, what about (IA and IB)? |
Beta Was this translation helpful? Give feedback.
-
I'm super excited about this, as I'm using One of the biggest problems that I have with OneOf now, is mixing async with non-async operations, and I wonder if union types will actually handle this. So what I'm looking for is:
Is this flow something I could expect? Or can I keep dreaming? 🌨️ |
Beta Was this translation helpful? Give feedback.
-
Is it necessary for unions to implement union struct U
{
A(int x, string y);
B(int z);
C = default;
}
union record struct U
{
A(int x, string y);
B(int z);
C = default;
}
union struct U
{
record A(int x, string y);
B(int z);
C = default;
} Of course, currently using |
Beta Was this translation helpful? Give feedback.
-
Another thing (I post a separate comment because it's a different thing). u switch {
A a => a.x,
B b => b.z,
C c => 0
} Is translated to u.Kind switch {
U.UnionKind.A when u.TryGetA(out var a) => a.x,
U.UnionKind.B when u.TryGetB(out var b) => b.z,
U.UnionKind.C when u.TryGetC(out var c) => 0,
_ => throw ...;
} But it's checking the union kind twice (once in the switch, the other inside the if (u.TryGetA(out var a))
return a.x;
else if (u.TryGetB(out var b))
return b.z;
else if (u.TryGetC(out var c))
return 0;
else
throw ...; If this method doesn't allow the usage of jump tables, then it could be instead: u.Get(out var a, out var b, out var c) switch {
U.UnionKind.A => a.x,
U.UnionKind.B => a.z,
U.UnionKind.C => 0,
_ => throw ...,
} That is, the compiler creates a method which has an |
Beta Was this translation helpful? Give feedback.
-
Link #8428 so that it can be included in future LDMs. TL;DR: it proposed a way to address type testing against boxed struct unions without harming performance in ordinary cases, with together a marker interface that can also be used to bring the language support to existing libraries like |
Beta Was this translation helpful? Give feedback.
-
I have come up with a situation, I don't know if you have considered it or not Foo<T>(this T t) where T : IA , IB IA a=new A();
if( a is IB b)
{
b.Foo()
} |
Beta Was this translation helpful? Give feedback.
-
Result being a struct type, #1239 Could allow for Aliasing Result<T,E> as a shorthand for domain/namespace specific Result types: It would feel like Rust with this syntax: I'm just stating this because this proposal being available at the same time Union arrives would reduce the boilerplate around methods. And it could make the feature more appealing because if shipped with proper examples to a new Error Handling mechanism (Error as value), people wouldn't fear the bigger return type names.
I can also highlight it better with a more abusive example representing some database Io error but with a concrete optional type rather than a nullable returned type: One way to make this even shorter could be with That way, we can define our 'own types' to essentially be alias on already existing structures with all their own set of extensions. And essentially enjoy more the BCL types and their features (when it's about structures in this case) |
Beta Was this translation helpful? Give feedback.
-
Excellent proposal. I'm looking forward to using Type Unions in C#. My question is specifically about the common unions. How will we handle multiple variables set from some service? Take this scenario, for example. We have three services that return things. Now, we need to execute a method that uses all three of those variables. What is the proposal to check that all three variables are of type I do not like these ideas. Option<int> variableOne = serviceOne.GetOne();
Option<int> variableTwo = serviceTwo.GetTwo();
Option<int> variableThree = serviceThree.GetThree();
int total = 0;
if (variableOne is Some)
{
if (variableTwo is Some)
{
if (variableThree is Some)
{
total = SumValues(variableOne, variableTwo, variableThree)
}
}
}
return;
int SumValues(int a, int b, int c)
{
return a + b + c;
} Or this: Option<int> variableOne = serviceOne.GetOne();
Option<int> variableTwo = serviceTwo.GetTwo();
Option<int> variableThree = serviceThree.GetThree();
int total = variableOne switch
{
Some: value =>
{
variableTwo switch
{
Some: valueTwo =>
{
variableThree switch
{
Some: valueThree =>
{
return SumValues(valueOne, valueTwo, valueThree);
},
None: => 0;
}
},
None: => 0
}
},
None: => 0
};
return;
int SumValues(int a, int b, int c)
{
return a + b + c;
} I'm curious about how this is going to look. Has any thought gone into how we will consume the |
Beta Was this translation helpful? Give feedback.
-
This proposal is being presented to LDM today. It is not a plan or record. It has not yet been accepted. When it, or equivalent is accepted it will go through many changes before becoming an actual feature. Your feedback is welcome and expected.
Type Unions in C#
I or other team members will probably be able to answer questions later today.
Beta Was this translation helpful? Give feedback.
All reactions