18 ms·
Official proposal for Type Unions in C#
- zamalek 2y ago> The interior layout of the union struct is chosen to allow for efficient storage of the data found within the different possible member types with tradeoffs between speed and size chosen by the compiler. Having _attempted_ (and being bitten by) far too much black magic with C# unions in the past (using FieldOffset), there is an unfortunate situation here: aliasing a pointer/ref value and value is UB. This means that a struct union of a u64 and an object would need separate fields for each, wasting 8 bytes. That is, unless the ryujit/gc is updated with knowledge about this.
- jasomill 2y agoNot UB, illegal. Per ECMA 335, II.10.7: It is possible to overlap fields in this way, though offsets occupied by an object reference shall not overlap with offsets occupied by a built-in value type or a part of another object reference. Per .NET 8.0: using System.Runtime.InteropServices; var s = new S { Bar = "Baz" }; [StructLayout(LayoutKind.Explicit)] public struct S { [FieldOffset(0)] public UInt64 Foo; [FieldOffset(0)] public Object Bar; } compiles, but throws System.TypeLoadException: Could not load type 'S' from assembly 'foo, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' because it contains an object field at offset 0 that is incorrectly aligned or overlapped by a non-object field. Interestingly, this code elicits a warning ILC: Method '[foo]Program.<Main>$(string[])' will always throw because: Failed to load type 'S' from assembly 'foo, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' because of field offset '0' from the AOT compiler, but none from the C# compiler.
- jaredpar 2y ago> from the AOT compiler, but none from the C# compiler. The C# compiler has very little knowledge of `[FieldOffset]`. It's expected that developers understand the runtime implications of this.
- tombert 2y agoHuh, I did F# for years with discriminated unions, and I guess I just assumed C# would have had them by now. I know not everyone likes them, but for typed languages I find it extremely hard to go back to languages without ADTs of some kind. I do Java for my current job, and Java is generally fine enough, but it's a little annoying when I have to do whacky workarounds with wrapper classes to get something that would be done in three lines of F#.
- Eji1700 2y agoF# is so hard to walk back from. I wish Microsoft would support it better and actually push it, because it's such a perfect sweet spot. Most of the functional advantages without being shackled to pure functions and the like is so easy to develop in. Instead they've been, very slowly, turning C# into F#, which is even weirder to watch.
- ctenb 2y agoIt makes sense from a language adoption standpoint, especially if you look at typescript for example, which is also by Microsoft. Though I agree with you personally
- tombert 2y agoI agree; F# is probably my favorite of the "compromise" functional languages [1], where you can drop into the "wrong" way when necessary. F# pushes a more or less pure approach, but in some cases, like when mutation will be a bit easier and/or faster, it's easy to do that as well. I also think that even F#'s OOP is actually really pleasant...If nothing else, it's a lot more terse than C#'s. I miss writing it; I haven't had a job using it in a few years but I really enjoyed writing code in it when I did. Most of my personal projects haven't really been able to use .NET for awhile, so I never seem to have an excuse to play with F# in my personal time. I never got into C#, so I can't say that I really feel the pain of the C# transition into F#. [1] At least in the typed world. I'm also really partial to Clojure.
- kriiuuu 2y agoI feel the same about Scala. I use Scala3 daily and almost every other language is such a step back. I have looked at F# and it looks like one of the only other languages I would enjoy as much as Scala. Haskell is nice too, but the ecosystem is just not quite there. F# being able to tap into the rich C# ecosystem and Scala being able to tap into the Java ecosystem is such a win and makes them feel a lot less niche when you use them.
- arwhatever 2y agoI’ve lost track of all of the red/blue/white/black pill color metaphors, but unions with exhaustive pattern matching is one of the toughest of all programming language features to live without once you become aware of it. I’ve never felt like I’ve fully understood the implications of the expression problem https://en.wikipedia.org/wiki/Expression_problem https://en.wikipedia.org/wiki/Expression_problem but my best/latest personal hypothesis is that providing extension points via conventional polymorphism might be best suited for unknown/future clients who might extend the code, but unions with exhaustive pattern matching seem better suited for code that I or my team owns. I don’t typically want to extend that code. More often, I instead want to update my core data structures to reflect ongoing changes in my understanding of the business domain, more often than not using these Union-type relationships, and then lean on the compiler errors maximally to get feedback about where the rest of the imperative code now no longer matches the business domain data structures.
- peheje 2y agoMaybe I'm off, but to me the gist of the expression problem can be explained by contrasting how code extensibility is achieved in OOP/FP. OOP Approach with interface/inheritance: Easy: Adding new types (variants) of a base class/interface. Hard: Adding new functionality to the base class/interface, as it requires implementing it in all existing types. FP Approach with Discriminated Unions: Easy: Adding new functions. Create a function and match on the DU; the compiler ensures all cases are handled. Hard: Adding new types to the DU, as it requires updating all existing exhaustive pattern matches throughout the codebase. Here's some Kotlin code. Kotlin is great because it can do both really well. // Object-Oriented Approach interface Shape { fun area(): Double fun perimeter(): Double } class Circle(val radius: Double) : Shape { override fun area() = Math.PI \* radius \* radius override fun perimeter() = 2 \* Math.PI \* radius } class Rectangle(val width: Double, val height: Double) : Shape { override fun area() = width \* height override fun perimeter() = 2 \* (width + height) } // Easy to add new shape class Triangle(val a: Double, val b: Double, val c: Double) : Shape { override fun area(): Double { val s = (a + b + c) / 2 return Math.sqrt(s \* (s - a) \* (s - b) \* (s - c)) } override fun perimeter() = a + b + c } // Hard to add new function (need to modify all existing shapes) // interface Shape { // fun area(): Double // fun perimeter(): Double // fun draw(): String // New function // } // Functional Approach sealed class ShapeFP { data class CircleFP(val radius: Double) : ShapeFP() data class RectangleFP(val width: Double, val height: Double) : ShapeFP() } fun area(shape: ShapeFP): Double = when (shape) { is ShapeFP.CircleFP -> Math.PI \* shape.radius \* shape.radius is ShapeFP.RectangleFP -> shape.width \* shape.height } fun perimeter(shape: ShapeFP): Double = when (shape) { is ShapeFP.CircleFP -> 2 \* Math.PI \* shape.radius is ShapeFP.RectangleFP -> 2 \* (shape.width + shape.height) } // Easy to add new function fun draw(shape: ShapeFP): String = when (shape) { is ShapeFP.CircleFP -> "O" is ShapeFP.RectangleFP -> "[]" } // Hard to add new shape (need to update all existing functions) // sealed class ShapeFP { // data class CircleFP(val radius: Double) : ShapeFP() // data class RectangleFP(val width: Double, val height: Double) : ShapeFP() // data class TriangleFP(val a: Double, val b: Double, val c: Double) : ShapeFP() // }
- ComputerGuru 2y agoEvery time the question of preferred programming languages comes up, I'm usually in the extreme minority with my preferences being Rust (for small/fast) and C# (for productive and easy, where GC is acceptable), a combination that doesn't seem to appeal to too many people. But for the projects that fall right about in the middle where it could go either way and I could see either language working, I almost always pick rust with discriminated unions being the biggest reason. It's incredibly hard to go back to a language without even basic ADTs after seeing the light.
- wk_end 2y agoInstead of C#, why not F#? You get proper ADTs among many other great features, but you still get all the utility of the .NET ecosystem.
- fluoridation 2y agoI get the feeling F# is a second-class citizen in the ecosystem. How likely is it (or is it at all possible) that I'm going to run into some library that doesn't work with F#?
- nightski 2y agoNone, it's a CLR after all. That said many libraries may not utilize the best features F# has to offer.
- antonyt 2y agoIt's impossible for a .NET library to not work with F#. It's very possible (and even likely) for a .NET library to prevent you from writing idiomatic F#. You're right that it's a second-class citizen.
- debugnik 2y ago> It's impossible for a .NET library to not work with F#. This isn't true, C# has been adding new ABI features that didn't interact correctly with F# until the compiler and tooling catched up. For example, the spanification of C# was a huge a pain point and it still is when it comes to tooling.
- orra 2y agoIs the terminology slightly off? AIUI TypeScript has type unions. But this looks like a discriminated union which I'd recognise from F# or Haskell. The distinction I'd draw is that for a DU there are named case constructors. Yes?
- wk_end 2y agoTypeScript has "union types". This proposal refers to them as "ad hoc unions". I don't think "type union" is a term of the art - at least I haven't heard it before. It seems to be something the C# people are making up to describe sum types.
- SideburnsOfDoom 2y ago> I don't think "type union" is a term of the art "sum type" is the term of art in Computer Science theory, but "union type" is also used. See https://en.wikipedia.org/wiki/Type_theory#Sum_type https://en.wikipedia.org/wiki/Type_theory#Sum_type https://en.wikipedia.org/wiki/Tagged_union https://en.wikipedia.org/wiki/Tagged_union
- zarathustreal 2y agoAt this point, calling it by its correct name (Sum Types) would leave them looking stupid for not using the correct name (Product Types) for their “records”
- jcparkyn 2y agoBut then what name would they use for tuples, which are distinct from records?
- cies 2y agoboth tuples and record (structs) are product types. tuples are "untagged", records/structs are "tagged" (the members have names). sum types also have tagged and untagged variants. the untagged variants are less useful (more cumbersome) in case of sum types and hence often are not implemented in languages.
- LandR 2y agoHa! Under covariance / contra variance > Note: Have Mads write this part. :)
- lyu07282 2y agoMads Torgersen, lead designer of C#, just to clarify lol
- wackro 2y agoWhat would the rough timeframe be for seeing adoption of this into the language? I was considering introducing the OneOf library into our codebase, but if this is < a year or so away it might not be worth the effort.
- leosanchez 2y ago3 years away at least.
- SideburnsOfDoom 2y agoThe linked says "Proposed, Prototype: Not Started, Implementation: Not Started, Specification: Not Started" .NET releases are every November, and I would be very surprised to see this in November 2024. More likely November 2025 at soonest. But check back to that page later.
- ygra 2y agoDefinitely no way for Nov 2024 since they're already no longer merging features for that release. Considering the scope of this, I'm doubtful for next year as well. .NET takes their time with features to get them right as well. They don't seem to take as long as Java, but maybe most new features are also not as widely publicized.
- louthy 2y agoYou won't be able to leverage C#'s pattern-matching effectively with a library. Really though, you don't need a library to do sum-types in C#: It's best to just use records for now: public abstract record Maybe<A> { private Maybe() { } public sealed record Just(A Value) : Maybe<A>; public sealed record Nothing : Maybe<A>; } The private constructor and sealed case-types stops anybody else deriving from `Maybe<A>`, so the type is effectively closed and finite. You can then construct like so: var mx = new Maybe<int>.Just(123); var my = new Maybe<int>.Nothing(); Then you can use the pattern-matching: var r = mx switch { Maybe<int>.Just (var x) => x, Maybe<int>.Nothing => 0 }; Of course, we don't get exhaustiveness checking, that'll have to wait for the proper sum-types (although the compiler does some pretty good work on this right now); but until then this is the most expressive and powerful way of doing sum-types in C#. And, if you want a load of pre-built ones, then my library language-ext will help [1] [1] https://github.com/louthy/language-ext/ https://github.com/louthy/language-ext/
- deleted 2y ago[deleted]
- ctenb 2y agoI find it strange and slightly confusing that tey call it "union types". The correct term would be "sum types" or "discriminated union types", union types being the union of two types with no distinctive tag between the two cases. E.g. |Int ∪ Int| ≡ |Int|, while |Int + Int| ≡ 2 |Int|
- lolinder 2y ago> I find it strange and slightly confusing that tey call it "union types". The correct term would be "sum types" or "discriminated union types"... You mean like this? > A proposal for type unions (aka discriminated unions) in C#. Discriminated unions are a type of union type, hence the name, and in terms of how they're used in everyday development they fill a very similar role. If they have no intention of supporting pure TypeScript-style unions (which I'm sure they don't) then I don't know what the harm is of shortening "discriminated union" to "union" in the context of C#.
- ctenb 2y agoI've read the proposal, and I'm just pointing out that "aka discriminated unions" is incorrect or at the very least confusing. Call it "sum types" if you don't like the term "discriminated union types", it's both correcter and shorter than "union types"
- noelwelsh 2y agoAFAICT from a quick skim of the proposal is actually includes both sum types (called union types) and union types (called ad hoc unions) under the same proposal. This is, to me, very confusing. They work in very different ways.
- jsmith45 2y agoAt runtime every value of any of these types are tagged in some some way. The struct based ones with an explicit tag member that is not visible at the language level. All the rest by way of the object's runtime type (v-pointer). Which means the fact that some look at a language level like a traditional closed sum type, and others look more like a union type is pretty much just that, looks. Even the "ad hoc unions" are basically functioning as sum types here, just an ad hoc sum type whose type constructors are other types, and abusing the fact that classes or boxed structs all have a vptr that can be used as the discriminator. This all compiles down to code that pattern matches on either the explicit tag member, or on the runtime type, and after the pattern match you basically have a normal type to work with. Hence why they are lumping it all together.
- ctenb 2y agoI'm currently using nested records with a private constructor in combination with the nuget package https://github.com/shuebner/ClosedTypeHierarchyDiagnosticSuppressor https://github.com/shuebner/ClosedTypeHierarchyDiagnosticSup... to make sure the switch types do not require a `_` case. This is essentially the desugared version of their "Union Classes" proposal. This already works very well. Still, I like this proposal because it would be nice if the nuget package would become unnecessary and the syntactic sugar is also nice to have.
- S04dKHzrKT 2y agoDo note that record types aren't closed even with a private constructor. The compiler will generate a protected constructor for copy operations which can then be inherited from. To prevent this, you have to define your own protected constructor and have it throw a runtime exception if it isn't a valid case.
- ctenb 2y agoYou are technically correct (the best kind of correct), but in practice I've not found this to be a problem, certainly not something that is ofsetting the vast usefulness of this data pattern.
- S04dKHzrKT 2y agoI've found the same to be true as well.
- sarmudi08 2y ago[flagged]
- Alifatisk 2y agoEven dotnet is looking at implementing type unions, why can't Dart finally agree on adding this aswell!?
- munificent 2y agoWe added sum types and exhaustive pattern matching in Dart 3.0. Union types (what the proposal here calls "ad hoc unions") are a separate, much harder feature whose value proposition is less clear. The cost of adding a new kind to the type system is quite large because it impacts method resolution, overriding, type inference, subtyping, least upper bound, generics, type promotion, etc. It can be worth it (for example, we added tuple/record types in Dart 3.0), but the value proposition must be correspondingly high to justify it. It's not clear that union types meet that bar yet. In many if not most of the places where users are asking for it, it often seems like what they really want is overloading or some other feature.
- Alifatisk 2y agoThanks, I appreciate the explanation a lot! I find losing the type hints because I am forced to type something as "dynamic" is a bummer, I hope exhaustive pattern matching solves cases like these: https://github.com/pocketbase/dart-sdk/blob/master/lib/src/auth_store.dart#L9 https://github.com/pocketbase/dart-sdk/blob/master/lib/src/a... Related issue: https://github.com/dart-lang/language/issues/83 https://github.com/dart-lang/language/issues/83
- tubthumper8 2y agoCan anyone explain why this is called "type unions"? I've never heard it named that before. It's a bit weird, because it's not a union across types (like ALGOL68), it appears to be a tagged union like in ML-family languages. Is this just a case of "C# developers like to make up different names than established terminology"? (see: SelectMany, IEnumerable, etc.)
- kgeist 2y ago"Tagged" sounds like an implementation detail to me (that it has a "tag" internally to tell between types). I suspect they added "type" to "union" to make it clear what is about (about types). The syntax itself has just "union", judging by the document. UPD. They have this in the FAQ: Q: Why are there no tagged unions? A: Union structs are both tagged unions and type unions. Under the hood, a union struct is a tagged union, even exposing an enum property that is the tag to enable faster compiler generated code, but in the language it is presented as a type union to allow you to interact with it in familiar ways, like type tests, casts and pattern matching.
- tubthumper8 2y agoI don't think tag is an implementation detail generally, for example: enum Option<T> { None, Some(T), } The tags are "None" and "Some" which are definitely user facing. But I see what they mean a bit with the C# example: union U { A(int x, string y); B(int z); C; } So it seems like "A", "B", and "C" are tags but also their own distinct types, with a implicit conversions between those and the overall union type
- tialaramex 2y agoTagged unions are an implementation detail and are often not the optimal way to solve this problem. Take Option<Infallible>, it's a ZST, not only is there no "tag" there isn't any data at all. In type theory this is fine, we added an empty type to the unit type, we got a unit type.
- 2y ago
- LAC-Tech 2y agoI always think it's a shame that instead of C# becoming a better OO language, they keep trying to become an uglier F#. IE, why is the syntax for multiple dispatch still so clunky? I know I know, pseudo OO took over the world, and then people revolted against it, so the easiest thing to do to stay relevant is to become "slightly functional with curly braces", rather than to actually try and do OO well.
- mrkeen 2y agoI have the same objection but coming from the other side. The mainstream languages used to have pervasive mutation and side-effects. Now they have pervasive mutation and side-effects with lambdas. But my question is: what is missing from the OO side? What would it take for C# and/or others to "do OO well"?
- LAC-Tech 2y agoTo be clear I don't really come from "the other side". I very much enjoy and like functional programming. But again, that's what F# is for, right? If we push the state of the art in another directrion, the whole landscape benefits more - because I think we both agree C# is never going to be a better FP language than F#. As for what C# could do better, the OO solution to the problem pattern matching solves is multiple dispatch - with single dispatch languages you need things like the visitor pattern. Making that a first class part of C# would be fantastic. https://shawnhargreaves.com/blog/visitor-and-multiple-dispatch-via-c-dynamic.html https://shawnhargreaves.com/blog/visitor-and-multiple-dispat...
- lyu07282 2y ago> What would it take for C# and/or others to "do OO well"? Multiple inheritance. (j/k)
- LAC-Tech 2y agoUnironically this. Having "interfaces" and "abstract classes" is just a kludge, multiple inheritance covers all of this with one language construct. The "diamond problem" is a C++ issue, every other language solved it.
- pipeline_peak 2y agoIt’s ironic when things like this get added back into C-styled languages that were intended to be easier than their predecessors. Seems more confusing than useful. Wish they’d stop adding onto this ever expanding language. It’s like these people get bored of plain old functions and objects, read an academic paper and go “i think I’ll add that next”.
- celdon25 2y ago> Seems more confusing than useful. Are you suggesting that these design decisions make it through the entire process and into the language without the feature bringing a clear advantage in its use case? What would you say are the worst two or three of these C# features approved in the last decade? Let’s assume for the sake of argument that you are correct though. The Roslyn compiler diagnostics are second to none, and pretty much any of these features will have corresponding analyzers to help remove some of that confusion, with documentation links being automatically provided if used incorrectly. These guys aren’t writing feature proposals the way you write comments.
- pipeline_peak 2y ago> These guys aren’t writing feature proposals the way you write comments. You’re comparing the effort of a mainstream language design proposal to a pseudonymous internet comment, wow, just wow. Do you truly know real software developers using any these new features? I must not be working anywhere cool because I’m always having to explain what they mean in code reviews. Imagine writing a full C# compiler in 2024, imagine if one possibly could with all these different features in this big big language. > Are you suggesting that these design decisions make it through the entire process and into the language without the feature bringing a clear advantage in its use case? I’m suggesting they don’t end up being adopted by enough people to be convincing. To me that leads to the shared problem with cpp, feature bloat. Multiple ways of achieving the same thing. Since you asked, these are the three off the top of my head. 1. Init keyword is strange, why specify an accessor that only seems to apply during initialization? We already have read only for constructors. I always felt initializing objects with brackets (whatever they hell they call it) was something meant for more casual code where assignment isn’t enforced or restricted. 2. Switch expressions are ridiculous. They can get hard to read, alienating non C# programmers who come from C styled backgrounds, part of what made this language successful to begin with. They seem more like yet another syntax sugar we have to learn. And to your Roselyn comment, a language shouldn’t be dependent on Roselyns analysis to actually be usable to the general public. That is not what I’d call a cross platform language, that’s a monopolistic one where Windows/Visual Studio is the only place a not .net fanatic such as yourself can easily get work done. 3. Nullable Reference Types further over complicates what used to be a simple system of reference and value types. On top of the already head scratching nullable value types which made people question why they initially moved away from javas only reference type concept to begin with. Slightly off topic but we have at least 3 ways to define record types: tuples, structs, and now records. I actually do like record types but unfortunately now existing code bases are polluted with the former. The point I’m trying to make in this last statement is I feel they aren’t careful enough with their proposals. They got it right with records, but look what they brought us along the way.
- riizade 2y agoI don't have anything insightful to add, I just want to add my voice to the choir of people saying that after using sum types, working in a language without them feels unnecessarily restrictive and awkward. If this feature were implemented, it would take C# to a top contender for a daily driver language for me, and would make me feel a lot more confident in choosing it for projects where the C# ecosystem is already present, such Godot's C# version. I'm really excited about this proposal, and I don't know anything about C#'s development pace, but I'll be watching from the sidelines hoping I can use union types in production in 2025 or so.
- deleted 2y ago[deleted]
- jtrowbridge 2y agoWhat's the difference between this proposal and sealed classes in Kotlin? Looking at the definitions in the proposal these look functionaly similar. Haven't worked with C# in some years so suprised that this isn't already a language feature.
- mlhpdx 2y agoI very much hope to see this (or similar) in C# soon. It’s not make or break for me, but it would certainly help make for cleaner, smaller codebases — I’ve sorely missed it in the past, but only a couple times. My context is being a former hard-core C++ person who switched to .Net and AWS pretty-much cold turkey. Edit: typo
- facturi 2y agoThat proposal doesn't mention how the union struct handles tearing under concurrent modification. Tearing can cause memory safety issue. Variant A can have an integer field and variant B can have a reference field in the same offset. Tearing can cause it to treat an integer as reference.
- facturi 2y agoTearing issue is mentioned in this issue https://github.com/dotnet/csharplang/issues/7016 https://github.com/dotnet/csharplang/issues/7016 but not in proposal
- andygocke 2y agoI’m the author of the original issue — I agree, we’ll have to ensure the struct layout is solved. I think the only thing that makes sense is to just waste a little space and store the fields side-by-side. In almost all cases where people would use a struct I think this is an acceptable tradeoff. At the point where you have more than 5 cases, the GC overhead starts to get shrink in comparison to the calling convention and copying overhead anyway.
- neonsunset 2y agoWill there be any layout optimizations performed by Roslyn for fields that can be aliased? E.g.: for a type union which has variants with 2 object fields and one ushort-sized at most each, have a base layout of (object, object, short)?
- jaredpar 2y agoThe design is not going to allow for struct layout to have memory safety issues when tearing is involved. That is a problem we are very well aware of and consider in designs.
- kkukshtel 2y agoReally excited for this proposal as it's been the main thing I have to apoligize for when extolling the values of C#. Outside of this it's hard to think of other major features C# lacks for a language. Additionally, excited to now watch this go in and people on HN still act like C# is the same it was 10 years ago.
- codr7 2y agoProper type aliases would be nice, but I have to say I really enjoy coding C# these days.
- neonsunset 2y agoWhy hello again :) I really appreciate your projects and noticed the web server one. There's an ask - please prefer built-in containers unless you have to use custom ones, at least for now. It is also not necessary to define aliases for types accessible directly: `using Namespace.AnotherNamespace` is enough to access them. There is no need to re-define the alias for `AnotherNamespace` if it matches either. When you have time, please look at sharpl PR which simplifies the implementation and makes it more compiler-friendly: https://github.com/codr7/sharpl/pull/2 https://github.com/codr7/sharpl/pull/2 In any case, welcome to C# and thanks!
- codr7 2y agoHello there :) Just a little bit of patience with the collections, I'm slowly learning to trust the native offerings, at least there's a regular List at the bottom now, much thanks to you. I'm just used to using more descriptive type names, that's all. It bothers me to no end when I have to keep guessing and memorizing the correct primitive type for a concept. Also makes the code more difficult to read. I'll have a look at the PR, thank you!
- kkukshtel 2y agoAlias-ing any type was added in C# 12 - do you mean something more than this? https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-12#alias-any-type https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/cs...
- yas_hmaheshwari 2y agoDoes anyone also feel that any new feature in C# changes to a flame war between Java and C# :-) I expected that before clicking the link, and came prepared :popcorn:
- arnath 2y agoAs a long time C# dev, I feel like I’m missing something about this proposal. The use case for this doesn’t seem well defined to me. Could someone give me a real world example? I’m pretty sure you can implement the example in this proposal by declaring an empty interface and having some record classes that “implement” it. Does that lose something that I’m not seeing?
- alkonaut 2y agoThat type of hierarchy is “open” in the sense that when you handle them you can’t know whether you handled all of them. Anyone can make a new implementation of your interface, potentially in code that isn’t yours. Also, the options may not share any surface area at all, so the “interface” for all the types may be empty which is a bit unnatural in OO. But under the hood, it’s just this: a type hierarchy. But the hierarchy is closed for extension, and (this is the key part you’d not get if doing this “manually” as you describe) when code uses the cases, failing to account for a case will cause an error at compile time.
- Renaud 2y agoI found this video quite useful in showing the new proposal in action. https://youtu.be/aksjZkCbIWA https://youtu.be/aksjZkCbIWA
- larusso 2y agoI think the most down to earth example is the AST or data protocol examples. Take Jason for example. Instead of having a JsonValue class to hold the data you would have a Union type that is either string, bool, number, array of your union type or map type which uses a string as key and the same union type as value. It would also allow to implement result types like rust has them. So you could define an API that returns a value or an error. I didn’t see in the proposal if they talked about generics though. In functional programming world this is mainly known as algebraic data types. I can say that if you get used to model types like this you really start to miss it in languages that don’t support it. [1] https://en.m.wikipedia.org/wiki/Algebraic_data_type https://en.m.wikipedia.org/wiki/Algebraic_data_type
- bazoom42 2y ago
- nsonha 2y agohow do people writing C# deal with its gazillian features? It seems easier (and more useful) to learn and master every language that it absorbs, rather than C# itself.
- rr808 2y agoIs this just a Variant like VB? (and VB.net)
- AndrewDucker 2y agoNo - because those aren't limited to just a few specific types.
- rr808 2y agoOK yes of course, its like you can make your own Variant style types.
- gregsn 2y agoregarding ad hoc unions the article mentions this: Siamese pet = ...; (Cat or Chihuahua) mostlyCats = pet; Dog dog = (Dog)mostlyCats; Note: This works for implemented interfaces too. I am unsure what that section entails... Let's say I have a function like this: void ConsumeAB<T>(T ab) where T: IA, IB; And implementing classes like this: class ABC : IA, IB {} class ABD : IA, IB {} Will I be able to use this function like this: var items = new (ABC or ABD)[] { new ABC(), new ABD() }; foreach (var item in items) { ConsumeAB(item); } In other words will the ad hoc union act as it would implement all interfaces that all cases have in common? My guess it won't work, as it gets lowered to code that uses object. Would a custom union help me here?