Explore union types in C# 15
67 points by 0x00C0FFEE 4 days ago | 52 comments

mwkaufma 8 minutes ago
Looks like it's "just" type-erasure / syntactical sugar. E.g. value types are boxed.
reply
karmakaze 3 days ago
I haven't read this in detail but I expect it to be the same kind of sealed type that many other languages have. It doesn't cover ad-hoc unions (on the fly from existing types) that are possible in F# (and not many non-FP languages with TypeScript being the most notable that does).
reply
owlstuffing 2 hours ago
> 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 light and day difference between tuples and nominal records.
reply
CharlieDigital 2 hours ago
IME, this is a good thing.

The problem with ad-hoc unions is that without discipline, it invariably ends in a mess that is very, very hard to wrap your head around and often requires digging through several layers to understand the source types.

In TS codebases with heavy usage of utility types like `Pick`, `Omit`, or ad-hoc return types, it is often exceedingly difficult to know how to correctly work with a shape once you get closer to the boundary of the application (e.g. API or database interface since shapes must "materialize" at these layers). Where does this property come from? How do I get this value? I end up having to trace through several layers to understand how the shape I'm holding came to be because there's no discrete type to jump to.

This tends to lead to another behavior which is lack of documentation because there's no discrete type to attach documentation to; there's a "behavioral slop trigger" that happens with ad-hoc types, in my experience. The more it gets used, the more it gets abused, the harder it is to understand the intent of the data structures because much of the intent is now ad-hoc and lacking in forethought because (by its nature) it removes the requirement of forethought.

reply
let_rec 2 hours ago
> ad-hoc unions (on the fly from existing types) that are possible in F#

Are you sure? This is a feature of OCaml but not F# IIUIR

Edit: https://github.com/fsharp/fslang-suggestions/issues/538

reply
orthoxerox 2 hours ago
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.

reply
SideburnsOfDoom 2 hours ago
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" ?

reply
pjc50 2 hours ago
OK then, what is the opposite of this, the adhoc union?
reply
Semaphor 2 hours ago
I don’t know for sure, but I’m guessing something like

(Dog, Cat) pet = new Cat();

So without defining the union with an explicit name beforehand.

reply
SideburnsOfDoom 43 minutes ago
Well, you can do this in c#:

  var someUser = new { Name = "SideburnsOfDoom", CommentValue = 3 };

What type is `someUser` ? Not one that you can reference by name in code, it is "anonymous" in that regard. But the compiler knows the type.

A type can be given at compile-time in a declaration, or generated at compile-time by the compiler like this. But it is still "Compiler-verified" and not ad-hoc or at runtime.

the type (Dog, Cat) pet seems similar, it's known at compile-time and won't change. A type without a usable name is still a type.

Is this "ad-hoc"? It depends entirely on what you mean by that.

reply
SideburnsOfDoom 2 hours ago
I don't follow the question. Maybe define the term that you are using?
reply
pjc50 2 hours ago
Top comment mentioned the term without defining it, confusing me and seemingly most of the thread: https://news.ycombinator.com/item?id=47649817
reply
dathinab 2 hours ago
it's basically `union <name>([<type>],*)`, i.e.

=> named sum type implicitly tagged by it's variant types

but not "sealed", as in no artificial constraints like that the variant types need to be defined in the "same place" or "as variant type", they can be arbitrary nameable types

reply
kkukshtel 14 minutes ago
LETS GOOOOOOOOOOOOOOOOOOOOOOOO
reply
jcmontx 36 minutes ago
So they finally took all of the cool features from F#. What's missing? The pipe operator for railway oriented programming?
reply
algorithmsRcool 24 minutes ago
Well off the top of my head...

Active patterns, computation expressions, structural typing, statically resolved type parameters, explicit inlining, function composition, structural equality, custom operators and much richer generators.

reply
JMKH42 27 minutes ago
type providers, units of measure, active patterns, complete type inference.

Not sure I would want the last thing in C#, I think having boundaries at the function signature for that.

reply
recursive 33 minutes ago
Units of measure
reply
owlstuffing 13 minutes ago
F# units are handy, but nothing like Manifold units (Java):

https://github.com/manifold-systems/manifold/tree/master/man...

reply
mirages 44 minutes ago
#define struct union
reply
FrustratedMonky 2 hours ago
Is this the last of the F# features to be migrated into C#?

What a missed opportunity. I think really F# if you combine all of its features, and what it left out, was the way. Pulling them all into C# just makes C# seem like a big bag of stuff, with no direction.

F#'s features, and also what it did not included, gave it a style and 'terseness', that still can't really be done in C#.

I don't really get it. Was a functional approach really so 'difficult'? That it didn't continue to grow and takeover.

reply
dathinab 10 minutes ago
> big bag of stuff, with no direction.

also called general purpose, general style langue

> that still can't really be done in C#

I would think about it more as them including features other more general purpose languages with a "general" style have adopted then "migrating F# features into C#, as you have mentioned there are major differences between how C# and F# do discriminated sum types.

I.e. it look more like it got inspired by it's competition like e.g. Java (via. sealed interface), Rust (via. enum), TypeScript (via structural typing & literal types) etc.

> Was a functional approach really so 'difficult'?

it was never difficult to use

but it was very different in most aspects

which makes it difficult to push, sell, adapt etc.

that the maybe most wide used functional language (Haskel) has a very bad reputation about being unnecessary complicated and obscure to use with a lot of CS-terminology/pseudo-elitism gate keeping doesn't exactly help. (Also to be clear I'm not saying it has this properties, but it has the reputation, or at least had that reputation for a long time)

reply
LunicLynx 53 minutes ago
You aren’t giving enough credit to the careful evaluation of how this adaption is happening.

So far everything that was added to C# very much reduces the amount of dead boilerplate code other languages struggle with.

Really give it an honest try before you judge it based on the summation of headlines.

reply
owlstuffing 3 minutes ago
In isolation, yes, I agree with you. But in the context of the cornucopia of other "carefully evaluated" features mixed into the melting pot, C# is a nightmare of language identities - a jack of all trades, master of none, choose your dialect language. No thanks.
reply
NanoCoaster 2 hours ago
Absolutely agree. Modern C# language design feels very much lacking in vision or direction. It's mostly a bunch of shiny-looking language features being bolted on, all in ways that make the language massively more complex.

Just look at this feature: https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/cs...

Was this needed? Was this necessary? It's reusing an existing keyword, fine. It's not hard to understand. But it adds a new syntax to a language that's already filled to the brim, just to save a few keystrokes?

Try teaching someone C# nowadays. Completely impossible. Really, I wish they would've given F# just a tenth of the love that C# got over the years. It has issues but it could've been so much more.

reply
LunicLynx 44 minutes ago
You are looking at it from what you know about C#, the goal is how can you reduce (delete) all this to make the language more accessible.

For you it may be fine to write:

List<string> strs = new List<string>();

And sure if you have been using C# for years you know all the things going on here.

But it shouldn’t be an argument that:

List<string> strs = [];

Is substentionally easier to grasp.

And that has been the theme of all changes.

The example you point out is the advanced case, someone only needs in a very specific case. It does not have a lot todo with learning the language.

The language design team is really making sure that the features work well throughout and I think that does deserve some credit.

reply
NanoCoaster 27 minutes ago
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 main language in my free time is Rust, a few years ago it was F#. So, I'm absolutely open to other syntax ideas. But I feel that there has to be a direction, things have to work together to make a language feel coherent.

Another example would be Clojure, which I started learning a few months ago (before we all got swept up in AI FOMO :D). Clojure as a language feels very coherent, very logical. I'm still a beginner, but every time I learn something about it, it just makes sense. It feels as if I could have guessed that it works this way. I don't get that feeling at all in many of the new features of C#.

> The example you point out is the advanced case, someone only needs in a very specific case. It does not have a lot todo with learning the language.

I disagree. When learning the language, you're going to have to read other people's code and understand it. It's the same basic principle, but, I'd argue, much worse in C++. Yes, in theory, you don't have to understand SFINAE and template metaprogramming and (now) concepts and all those things. You could just work in a subset of C++ that doesn't use those things. But in practice, you're always going to have issues if you don't.

reply
xnorswap 17 minutes ago
That's a basic example with a single level of generics too, you'd sometimes have to do things like:

    Dictionary<string, List<Tuple<string, string>>> foo = new Dictionary<string, List<Tuple<string, string>>> 
Or things like:

    Dictionary<string, List<Tuple<string, string>>> foo = DoSomeWork();
And you'd have to get the type right, even though the compiler knew the type, because it'd tell you off for getting it wrong. Sometimes it was easiest to just grab the type from the compiler error. ( This example is of course a bit OTT, and it's a bit of a code-smell to be exposing that detail of typing to consumers. )

No-one wants to go back to that, and anyone who says C# is over-complicated I think is forgetting how rough it was in the earliest versions.

While introduction of auto-typing through "var" helped a lot with that, you'd still regularly have to fight if you wanted to properly initialise arrays with values, because the syntax was just not always obvious.

Collection literals are amazing, and now the ability to pass things into the constructor means they can be used when you need constructor parameters too, that's just a good thing as you say.

reply
raincole 17 minutes ago
> The example you point out is the advanced case, someone only needs in a very specific case

This is exactly how C++ landed where it is now. Every time it's "you only need to know that syntax if..." well it ends up everyone has to know that syntax because someone will use it and if you're a responsible programmer you'll end up reading a lot code written from other people.

reply
twisteriffic 20 minutes ago
> Try teaching someone C# nowadays. Completely impossible.

That isn't a reasonable take. Failing to teach a language by enumerating all its features is an indictment of the instructor and not the language.

reply
NanoCoaster 2 minutes ago
I guess I overdramatized the situation a bit :) It's a passionate topic for me; as somebody who has been using C# at work for 10 years now, I'm just not happy with the direction the language has been taking.

You're right, it's not impossible and in general it's not among the hardest languages to teach. But I would argue, it is heading that way.

There are already so many ways to do things in C#. For example, try explaining the difference between fields and properties; sounds easy, but making it really stick is quite a challenge. And that's one of the simplest cases (and a feature I'm 100% in favor of).

And you will have to explain it at some point, because real codebases contain these features so at some point, it'll need to be taught. Learning a language doesn't stop when you can write a simple application, it continues up until at least you're comfortable with most of its features and their practical use. The quicker one can get people to that point, the easier the language is to teach, I'd argue.

One might also argue that learning never really stops, but that's beside the point :)

In general, my issue isn't any specific feature. C# has many features that are non-trivial to learn but still great: value types, generics, expression trees. Source generators are relatively new and I like them! I like most of the things they're doing in the standard library or the runtime. Spans everywhere is a nice improvement, most new APIs are sensible and nice to use and the runtime just keeps getting faster every release. Great. It's more the pure C# language side I have an issue with.

But every language has a budget of innovation and cognitive load that you can expect people to deal with, and C# is not using its budget very wisely in my opinion.

reply
raincole 46 minutes ago
> Try teaching someone C# nowadays. Completely impossible. Really, I wish they would've given F# just a tenth of the love that C# got over the years

If they actually put effort in F#, it would have reached "unteachable" state already :)

reply
NanoCoaster 21 minutes ago
Haha, yeah, maybe :)

I would've loved an F# that found a way to improve on the performance issues, especially when using computation expressions. That and, either, a deeper integration of .NETs native OOP subtyping, or some form of OCaml-like module system, would have been enough to make it an almost perfect language for my tastes.

Obviously, these are big, and maybe impossible, issues. But Microsoft as a whole never really dedicated enough resources to find out. I feel for the people still working on it, their work is definitely appreciated :)

reply
Pay08 24 minutes ago
This is why I have always been leery of C# and continued using Java instead. C#s development has always seemed very haphazard and kitchen sink mentality to me.
reply
CharlieDigital 2 hours ago

    > I don't really get it
To me it makes sense because C# is a very general purpose language that has many audiences. Desktop GUI apps, web APIs, a scripting engine for gaming SDKs, console apps.

It does each reasonably well (with web APIs being where I think they truly shine).

    > Was a functional approach really so 'difficult'
It is surprisingly difficult for folks to grasp functional techniques and even writing code that uses `Func`, `Action`, and delegates. Devs have no problem consuming such code, but writing such code is a different matter altogether; there is just very little training for devs to think functionally. Even after explaining why devs might want to write such code (e.g. makes testing much easier), it happens very, very rarely in our codebase.
reply
pjmlp 38 minutes ago
Microsoft's management has always behaved as if it was a mistake to have added F# into Visual Studio 2010, and being stuck finding a purpose for it.

Note that most of its development is still by the open source community and its tooling is an outsider for Visual Studio, where everything else is shared between Visual Basic and C#.

With the official deprecation of VB, and C++/CLI, even though the community keeps going with F#, CLR has changed meaning to C# Language Runtime, for all practical purposes.

Also UWP never officially supported F#, although you could get it running with some hacks.

Similarly with ongoing Native AOT, there are some F# features that break under AOT and might never be rewritten.

A lost opportunity indeed.

reply
raincole 49 minutes ago
Union is almost a net positive to C# in my opinion.

But I do agree. C# is heading to a weird place. At first glance C# looks like a very explicit language, but then you have all the hidden magical tricks: you can't even tell if a (x) => x will be a Func or Expression[0], or if a $"{x}"[1] will actually be evaluated, without looking at the callee's signature.

[0]: https://learn.microsoft.com/en-us/dotnet/csharp/advanced-top...

[1]: https://learn.microsoft.com/en-us/dotnet/csharp/advanced-top...

reply
twisteriffic 44 minutes ago
From the PoV of someone who uses C# every day those are very strange things to be upset about.
reply
zigzag312 43 minutes ago
I personally like the direction C# is taking. A multi-paradigm language with GC and flexibility to allow you to write highly expressive or high performance code.

Better than a new language for each task, like you have with Go (microservices) and Dart (GUI).

I'm using F# on a personal project and while it is a great language I think the syntax can be less readable than that of C#. C# code can contain a bit too much boilerplate keywords, but it has a clear structure. Lack of parenthesis in F# make it harder to grasp the structure of the code at a glance.

reply
owlstuffing 2 hours ago
> Pulling them all into C# just makes C# seem like a big bag of stuff, with no direction.

Agreed. Java is on the same trail.

reply
DarkNova6 2 hours ago
Care to elaborate? I think Java is showing remarkable vision and cohesion in their roadmap. Their released features are forward compatible and integrate nicely into existing syntax.

I work much with C# these days and wish C# had as cohesive a syntax story. It often feels like "island of special syntax that makes you fall of a cliff".

reply
98347598 2 hours ago
It's very disappointing that they aren't supporting Rust-style discriminated unions.
reply
_old_dude_ 2 hours ago
In C#, all instances have a class, so there is already a discriminant, the class itself.

In the article, the example with the switch works because it switches on the class of the instance.

reply
hnthrow0287345 2 hours ago
One step at a time
reply
gib444 2 hours ago
Is C# a great language trapped in a terrible ecosystem? ie would masses use C# if it existed in another ecosystem?

Or is it becoming a ball-of-mud/bad language compared to its contemporaries?

(Honest questions. I have never used .NET much. I'm curious)

reply
JCTheDenthog 57 minutes ago
Depends on what you mean by ecosystem, it hasn't been trapped on Windows for about a decade now. The variety of third party libraries available is quite good, while the standard library is robust enough that you don't need NPM nonsense like LeftPad and IsEven and IsNumber.

Are there particular things about the ecosystem that you worry about (or have heard about)? Biggest complaint I would have is that it seems like many popular open source libraries in the .NET ecosystem decide to go closed source and commercial once they get popular enough.

reply
gib444 41 minutes ago
Yup, the commercial libraries. That's pretty big. It's nice the standard library has lots of goodies, but I doubt many projects in reality are zero-dependency

(The amount of times I hear "the standard lib is great!" seems more to attempt to defend the plethora of commercial libraries, more than anything)

The community feels rather insular too? The 9-5 dayjob types with employers who don't understand or embrace open source? At my age I can respect that though

And is Postgresql a 2nd-class citizen? If so, your boss will tell you to use SQL Server surely?

I guess it's hard to get a grasp on the state/health of .NET as to me it seems 99.99999% of the code is in private repos companies, as it's not a popular choice for open source projects. Which itself seems like a proxy signal though

reply
CharlieDigital 26 minutes ago

    > And is Postgresql a 2nd-class citizen?
No, it is not.

Microsoft maintains the Npgsql project[0] and I say that it is a very capable, feature rich adapter.

I have not used C# with SQL Server in almost a decade.

[0] https://www.npgsql.org/

reply
CharlieDigital 38 minutes ago
C# is a language that serves many masters and if you trace the origin of its featureset, you can see why each was created. Take the `dynamic` keyword: created to support interfacing with COM interop easier[0].

It serves many audiences so it can feel like the language is a jack of all trades and master of none (because it is) and because it is largely backwards compatible over its 20+ years of existence.

That said, I think people make a mountain out of a molehill with respect to keyword sprawl. Depending on what you're building, you really only need to focus on the slice of the language and platform you're working with. If you don't want to use certain language features...just don't use them?

I think it excels in a few areas: web APIs and EF Core being possibly the best ORM out there. For me, it is "just right". Excellent platform tooling, very stable platform, very good performance, hot reload (good, but not perfect), easy to pick up the language if you already know TypeScript[1]; there are many reasons it is a good language and platform.

[0] https://learn.microsoft.com/en-us/dotnet/csharp/advanced-top...

[1] https://typescript-is-like-csharp.chrlschn.dev/

reply
gib444 35 minutes ago
> EF Core being possibly the best ORM out there

Is it good at the wrong thing? Eg compare to strongly-typed query generators

reply
CharlieDigital 27 minutes ago
It is a strongly-typed query generator?
reply
throwuxiytayq 29 minutes ago
It's a very nice language embedded in a very nice ecosystem. There is no catch, really.
reply