6 ms·
Union types in C# 15
- kkukshtel 6mo ago[flagged]
- karmakaze 6mo agoI 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).
- let_rec 6mo 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 https://github.com/fsharp/fslang-suggestions/issues/538
- orthoxerox 6mo agoThird 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.
- SideburnsOfDoom 6mo agoIt'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" ?
- pjc50 6mo agoOK then, what is the opposite of this, the adhoc union?
- SideburnsOfDoom 6mo agoI don't follow the question. Maybe define the term that you are using?
- pjc50 6mo agoTop comment mentioned the term without defining it, confusing me and seemingly most of the thread: https://news.ycombinator.com/item?id=47649817 https://news.ycombinator.com/item?id=47649817
- SideburnsOfDoom 6mo agoWe seem to have yet another potential meaning here : https://news.ycombinator.com/item?id=47692261 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.
- Semaphor 6mo agoI 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.
- SideburnsOfDoom 6mo agoWell, 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.
- owlstuffing 6mo 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 night and day difference between tuples and nominal records.
- Metasyntactic 6mo agoHi 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 :)). That's definitely not the case. We just like to break things over many versions so we can get the time to see how people feel about things and where are limited resources can be spent best next. We have wanted to explore the entire space of unions for a long time. Nominal unions. Anonymous unions. Discriminated unions. It's all of interest to us :)
- CharlieDigital 6mo agoIME, 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. "I am here. I need this additional field or this additional type. I'll just add it." This creates a kind of "type spaghetti" that makes code reuse very difficult. So even when I write TS and I have the option of using ad-hoc types and utility types, I almost always explicitly define the type. Same with types for props in React, Vue, etc; it is almost always better to just explicitly define the type, IME. You will thank yourself later; other devs will thank you.
- mikeocool 6mo agoYeah, Typescript feels like it had has arrived at the point where someone needs to write “Typescript: the good parts” and explains all of the parts of the language you probably shouldn’t be using.
- dathinab 6mo agoit'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
- mpawelski 6mo agoI'm pretty sure at one point there was proposal that allowed declaring something like `int or string`. Not sure what happened with it though.
- 98347598 6mo agoIt's very disappointing that they aren't supporting Rust-style discriminated unions.
- hnthrow0287345 6mo agoOne step at a time
- _old_dude_ 6mo agoIn 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.
- cobbal 6mo agoNull doesn't. `union(Int?, String?)` will only have 1 type of null, unlike a proper discriminated union.
- adrian_b 6mo agoThe 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 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, but this C# behavior still does not make the C# unions anything else but discriminated unions, even if with a peculiar feature.
- gf000 6mo ago> 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.
- FrustratedMonky 6mo agoIs 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.
- owlstuffing 6mo 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.
- DarkNova6 6mo agoCare 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".
- owlstuffing 6mo ago[dead]
- vips7L 6mo agoIt's honestly hilarious since the person you're replying to has heavily advocated Manifold which is a compiler extension to Java that adds every little feature to the language. https://github.com/manifold-systems/manifold https://github.com/manifold-systems/manifold
- CharlieDigital 6mo 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.
- gib444 6mo agoIs 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)
- JCTheDenthog 6mo agoDepends 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.
- gib444 6mo agoYup, 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
- CharlieDigital 6mo 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/ https://www.npgsql.org/
- mirages 6mo ago#define struct union
- jcmontx 6mo agoSo they finally took all of the cool features from F#. What's missing? The pipe operator for railway oriented programming?
- recursive 6mo agoUnits of measure
- owlstuffing 6mo agoF# units are handy, but nothing like Manifold units (Java): https://github.com/manifold-systems/manifold/tree/master/manifold-deps-parent/manifold-ext#unit-expressions https://github.com/manifold-systems/manifold/tree/master/man...
- JMKH42 6mo agotype 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.
- pjmlp 6mo agoYou can fake type providers with code generators though.
- algorithmsRcool 6mo agoWell 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.
- mwkaufma 6mo agoLooks like it's "just" type-erasure / syntactical sugar. E.g. value types are boxed.
- AndrewDucker 6mo agoYes, but see the section on custom unions* - you can write non-boxing unions/generators. * https://devblogs.microsoft.com/dotnet/csharp-15-union-types/#custom-unions-for-existing-libraries https://devblogs.microsoft.com/dotnet/csharp-15-union-types/...
- celeries 6mo agoYes, but that's just the default behavior. You can implement your own non-boxing version for performance critical applications.
- algorithmsRcool 6mo agoWhy on earth did they decide boxing by default was a sensible design decision... We have been pushing toward higher performance for years and this is a performance pitfall for unions would are often thought of as being lighter weight than inheritance hierarchies. F# just stores a field-per-case, with the optimization that cases with the same type are unified which is still type safe.
- zigzag312 6mo ago
- DeathArrow 6mo agoThis is HUGE! Now we can use mostly functional programming in C#. This feature was requested since many years ago. The only thing I wish now is for someone to build a functional Web framework for C#.
- littlecranky67 6mo agoMinimal API is pretty functional.
- DeathArrow 6mo agoI love it, but I see a downside, though: unions are currently implemented as structs that box value types into a Value property of type object. So there can be performance implications for hot paths.
- IcyWindows 6mo agoThe article mentions that one can implement a union type that doesn't do boxing.
- tialaramex 6mo agoI don't love OneOrMore<T> It's trying to generalize - we might have exactly one T, fine, or a collection of T, and that's more T... except no, the collection might be zero of them, not at least one and so our type is really "OneOrMoreOrNone" and wow, that's just maybe some T.
- CharlieDigital 6mo ago`OneOrMore<T>` was an example of using `union` types. You are free to call it `public union Some<T>(T, IEnumerable<T>)`
- gf000 6mo agoBut now you can only call methods that are available for both T and IEnumerable<T>, you have no way of knowing which it actually is. (You would know if it were sum types)
- rafaelmn 6mo ago> OneOrMoreOrNone So IEnumerable<T> ? What's up with wrapping everything into fancy types just to arrive at the exact same place.
- merb 6mo agoOneOrMore is more or less an example from the functional world. i.e.: https://hackage.haskell.org/package/oneormore https://hackage.haskell.org/package/oneormore or scala: https://typelevel.org/cats/datatypes/nel.html https://typelevel.org/cats/datatypes/nel.html it's for type purists, because sometimes you want the first element of the list but if you do that you will get T? which is stupid if you know that the list always holds an element, because now you need to have an unnecessary assertion to "fix" the type.
- dasyatidprime 6mo agoThe NonEmptyList in Cats is a product (struct/tuple) type, though; I assume the Haskell version is the same. The type shown in the blog post is a sum (union) type which can contain an empty enumerable, which contradicts the name OneOrMore. The use described for the type in the post (basically a convenience conversion funnel) is different and makes sense in its own right (though it feels like kind of a weak use case). I'm not sure what a good name would've been illustratively that would've been both accurate and not distracting, though.
- amluto 6mo agoHmm, 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 discriminated at all, so they’re not nearly as convenient). I personally much prefer the latter design.
- tialaramex 6mo agoThe 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<T> which is a union of a T and the empty tuple. Very, very, clever and valuable, but I didn't run into any similarly good uses outside that.
- masklinn 6mo agoUnions can be used as a somewhat safer (not safe by any means but safer), more flexible, and less error-prone form of transmute. Notably you can use unions to transmute between a large type and a smaller type. That is essentially the motivation, primarily in the context of FFI where matching C's union behaviour using transmute is tricky and error-prone.
- randomNumber7 6mo agoThere are rare cases where all attributes of the C union are valid at the same time. Say you have a 32-bit RGBA color value and you want to access the individual 8 bit values. You can make a union of an 32 bit int and a struct that contains 4x 8 bit integers. Also you can manually tag them and get s.th. more like other high level languages. It will just look ugly.
- merb 6mo agoSad part is, is that ad hoc unions probably won’t make it into v1. That is probably one of the only feature why I like typescript. Because I can write result types in a good way without creating thousands of sub types. It’s even more important when using Promises and not having checked exceptions.
- utf_8x 6mo agoHell yeah! After all these years it's finally here. One thing I miss here (and admittedly I only skimmed through the post so if I missed this, please do correct me) is "ad hoc" unions. It would be great to be able to do something like public Union<TypeA, TypeB> GetThing()... Without having to declare the union first. Basically OneOf<...> but built-in and more integrated
- debugnik 6mo agoI guess you can define union Union<T1, T2>(T1, T2); Add a bunch of overloads and you'd replicate for T1|T2 syntax the equivalent mess to (Value)Tuple<...> that eventually became the backing for actual tuple syntax.
- SuperV1234 6mo agoBoxed. Killed all my excitement.
- Turskarama 6mo agoYou should have kept reading. For performance-sensitive scenarios where case types include value types, libraries can also implement the non-boxing access pattern by adding a HasValue property and TryGetValue methods. This lets the compiler implement pattern matching without boxing.
- enbugger 6mo agoCool, you now can implement Elm architecture inspired GUI framework in C#. As much as I hate Microsoft, I admit they are doing great things for C#
- ArtCurator 6mo agoInteresting direction. It feels like languages are slowly moving towards more expressive and flexible type systems. Even outside of C#, this trend seems to show up everywhere — trying to reduce boilerplate while keeping things safe.
- gavinray 6mo agoThis is not "true" ad-hoc union support like TS or Scala This is essentially "sealed interface" from Java/Kotlin or "enum" in Rust