Live data from Hacker News

Union types in C# 15

devblogs.microsoft.com

161–170 of 215 posts

Re: Union types in C# 15

#161
post #13

Earlier quoted context omitted.

I don't follow the question. Maybe define the term that you are using?

Top comment mentioned the term without defining it, confusing me and seemingly most of the thread: https://news.ycombinator.com/item?id=47649817

We seem to have yet another potential meaning here : https://news.ycombinator.com/item?id=47692261

> Cat, Dog and Bird don't have to inherit from the union, you can declare a union of completely random types, as opposed to saying "Animal has three subtypes, no more, no less"

"Animal has three subtypes" is more like the c# "sealed" modifier on a class, meaning that subtyping is not allowed. Except in this case I guess for three existing subtypes.

Re: Union types in C# 15

#162

Earlier quoted context omitted.

Third paragraph from the top: > unions enable designs that traditional hierarchies can’t express, composing any combination of existing types into a single, compiler-verified contract.

It's very unclear which you mean by that. To me that "compiler-verified" maps to "sealed", not "on the fly". Probably. Their example is: public union Pet(Cat, Dog, Bird); Pet pet = new Cat("Whiskers"); - the union type is declared upfront, as is usually the case in c#. And the types that it contains are a fixed set in that declaration. Meaning "sealed" ?

I think your "sealed" is misleading here, as that is used for sum types in similar languages (java).

As the language designer notes in the comments, these are named unions, as opposed to anonymous ones, but they are also working on the latter.

"Sealed" is probably not the correct word to use here, as it would be sealed in both case (it doesn't really make sense to "add" a type to the A | B union). The difference is that you have to add a definition and name it.

Re: Union types in C# 15

#163
post #5

It's very disappointing that they aren't supporting Rust-style discriminated unions.

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 variable…

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.

Re: Union types in C# 15

#164
post #110

Earlier quoted context omitted.

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…

> 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.

Re: Union types in C# 15

#165

Earlier quoted context omitted.

> but sadly it is saddled with the perception that it is Windows-only, which hasn't been true for a decade In my experience it does not work very well outside of the sanctioned Linux distributions. Quirky heisenbugs and nonsensical crashes made it virtually unusable for me on Void. I doubt that's changed in the years that have since passed. > not necessarily a negative, because Windows is a decent OS Is a language ru…

> In my experience it does not work very well outside of the sanctioned Linux distributions. Quirky heisenbugs and nonsensical crashes made it virtually unusable for me on Void. I doubt that's changed in the years that have since passed. It's open source. Did you follow the spirit of Linux to file a bug report of as much sense of the crashes as you could make? Most OSS only supports as many distros as people are will…

> Most OSS only supports as many distros as people are willing to test

Linux distros don't differ too significantly from each other nowadays (systemd plus a different package manager most of the time), so I'm almost sure this is not the source of problems.

Nonetheless, I can only add that we have ridiculous slowdowns in some standard library network calls on Linux, and at that point it is just not true that it will "seamlessly run on Linux", unfortunately.

Re: Union types in C# 15

#166

Earlier quoted context omitted.

I'm 100% on board with the [] syntax. I'm not on board with adding the syntax for passing arguments to the constructor within that syntax. I agree that = [] is perfectly fine syntax. But I would definitely argue that: [with(capacity: values.Length * 2), .. is non-intuitive and unnecessary. What other language is there that has this syntax? Alternatively, is this a natural way of writing this? I wouldn't say so. My ma…

Hi there. Designer of this feature :D > is non-intuitive and unnecessary. intuitive is definitely in the eye of the beholder. When people saw: `HashSet people = [with(StringComparer.CaseInsensitiveComparer), .. group1, group2]` they found it understandable. And this was also much nicer than what they'd have to write today (which would bring them out of the nice declarative collection-expression space). Does that make…

Hi, thanks for answering :)

> Collection expressions made the language more coherent. Instead of 7 different ways of doing things (some of which were genuinely not efficient), we gave one uniform way of doing it.

I see your point on this. My dislike comes from a mixture of "I don't like how it looks" and "this language already has tons of features".

In terms of looks, I wish it could be more coherent with existing syntax.

List = new {1, 2, 3} and List = {1, 2, 3} are obviously taken up by anonymous types and blocks themselves. Would something like

List = new(capacity: 10)[1, 2, 3]

have been possible? It feels like a combination of target-typed new and the initialization syntax. It involves the "new" keyword, which everybody already associates with constructor calls. It's short. Obviously, I don't know if this even works, maybe there's a parsing issue there (aren't those the most annoying issues in language design haha).

> they found it understandable

Kind of in my experience. Me and the people I've shown this to can easily remember it, but we all agree that it doesn't look like obvious syntax to them. Those two things are quite different to me. Contrast this to something like target-typed new, which immediately made sense to the same people. One might argue that that's fine enough and maybe it is, but I think, the less I have to remember about a language's syntax, the better. I'm going to have to remember many many other things anyway, better keep my memory free for the details of SynchronizationContext and async flow :)

I'm obviously aware that you get tons of bikeshedding comments like this all the time, so I'm sure you've gone through this. But to me, this invented syntax would have been fine. I just don't like the one that actually got in.

Now, the necessity on the other hand: May just be the company I'm working at, but my personal experience has never been that this is a big issue. Sure, it's nice to not have to fall back to explicit initialization a few more times. But personally, this doesn't pass my threshold of "painful enough to warrant additional syntax".

That's the core of my issue: Most, maybe all, of the new features in the language are fine to me in isolation. I may bikeshed about the explicit syntax (see: this thread). But my main issue is that the sum of complexity in the language and the issues beginners have when learning it are steadily increasing. I see this all the time at work.

As you said, this is definitely subjective. And in the end, language design is a very subjective process and maybe C# just won't be for me in the long run. But I wish it would, because at its core I like it, and .NET, a lot. Which is why I will continue to speak for my (subjective) viewpoint.

Well, this turned into a bit of an incoherent rant. I appreciate you exposing yourself to the HN acid pit ;)

Re: Union types in C# 15

#167
post #106

Hmm, they seem to have chosen to avoid names to the choices in the union, joining C++ variants and (sort of) TypeScript unions: unions are effectively just defined by a collection of types. Other languages have unions with named choices, where each name selects a type and the types are not necessarily all different. Rust, Haskell, Lean4, and even plain C unions are in this category (although plain C unions are not di…

The C and Rust union types are extremely sharp blades, enough so that I expect the average Rust beginner doesn't even know Rust has unions (and I assume you were thinking of Rust's enum not union) I've seen exactly one Rust type which is actually a union, and it's a pretty good justification for the existence of this feature, but one isn't really enough. That type is MaybeUninit which is a union of a T and the empty…

In Rust's case, union types should only be used for FFI with a C ABI.

As for C, it is a sharp blade on its own.

Re: Union types in C# 15

#168

Earlier quoted context omitted.

> It doesn't cover ad-hoc unions Yes and no. C# unions aren’t sealed types, that’s a separate feature. But they are strictly nominal - they must be formally declared: union Foo(Bar, Baz); Which isn’t at all the same as saying: Bar | Baz It is the same as the night and day difference between tuples and nominal records.

Hi there! One of the C# language designers here, working on unions. We're very interesting in this space. And we're referring to it as, unsurprisingly, 'anonymous unions' (since the ones we're delivering in C#15 are 'nominal' ones). An unfortunate aspect of lang design is that if you do something in one version, and not another, that people think you don't want the other (not saying you think that! but some do :)). T…

Well, there is also the issue that some things get designed and then abandoned even thought some improvements were expected, dynamic typing from DLR, expression trees, for example.

Re: Union types in C# 15

#169

Earlier quoted context omitted.

From what I've read, this is for the first implementation of unions, to reduce amount of compiler work they need to do. They have designed them in a way they can implement enhancements like this in the future. Things like non-boxing unions and tagged unions / enhanced enums are still being considered, just not for this version.

This is the general pattern of how the C# team operates, IME. "Never let perfect be the enemy of good" Very much what I've seen from them over the years as they iterate and improve features and propagate it through the platform. AOT as an example; they ship the feature first and then incrementally move first party packages over to support it. Runtime `async` is another example.

In the meantime I still haven't done any project with nullable references, because the ecosystem has yet to move along. Same applies to ValueTask for async code.

Re: Union types in C# 15

#170

Earlier quoted context omitted.

The C and Rust union types are extremely sharp blades, enough so that I expect the average Rust beginner doesn't even know Rust has unions (and I assume you were thinking of Rust's enum not union) I've seen exactly one Rust type which is actually a union, and it's a pretty good justification for the existence of this feature, but one isn't really enough. That type is MaybeUninit which is a union of a T and the empty…

FYI small string optimizations are generally implemented using unions.

In Rust? The two I'm big fans of, CompactString and ColdString do not use unions although historically CompactString did so and it still has a dependency on smallvec's union feature

ColdString is easier to explain, the whole trick here is the "Maybe this isn't a pointer?" trick, ColdString might be a single raw pointer onto your heap with the rest of the data structure at the far end of the pointer, this case is expensive because nothing about the text lives inline, but... the other case is that your entire text was hidden in the pointer, on modern hardware that's 8 bytes of text, at no overhead, awesome.

CompactString is more like a drop-in replacement, it's much bigger, the same size as String, so 24 bytes on modern hardware, but that's all SSO, so text like "This will all fit nicely" fits inline, yet the out-of-line case has the usual affordances such as capacity and length in the data structure. This isn't doing the "Maybe this isn't a pointer?" trick but is instead relying on knowing that the last byte of a UTF-8 string can't have certain values by definition.

Post reply on HN