Possibility of Discriminated Unions in C#: Functional Programming in .NET

Ғылым және технология

The source code from this demo is available to sponsors at Patreon: / possibility-of-c-84426442
Curious about discriminated unions and their place in C#? That is a modeling tool often seen in functional programming. It lets you create a type that can be one of several predefined options but cannot be extended further. In other words, they offer a way to create a variable that can legitimately hold different types of data.
But, how does that translate into C# and .NET? And, by the same token, how does functional programming fit in? First of all, the current C# syntax does not support discriminated unions. But, as you will learn from the video, it doesn't take more than disciplined coding and the use of existing elements of the C# syntax - primarily records - to write clean functional code in the middle of your object-oriented domain model!
In this video, we will extend an ASP.NET Core application to demonstrate how a discriminated union can be a better fit than a traditional object-oriented model in certain situations. We show how this functional concept can be used to develop a complex feature, with the goal of making your code more expressive and manageable.
From tackling domain modeling to dealing with complex injectable strategies, this video takes you on a tour-de-force functional modeling in C#, based on the use of discriminated unions.
Don't miss out on this opportunity to deepen your programming skills with C# and .NET, learn about discriminated unions, and see the potential of C# and .NET functional programming.
Thank you so much for watching! Please like, comment & share this video as it helps me a ton!! Don't forget to subscribe to my channel for more amazing videos and make sure to hit the bell icon to never miss any updates.🔥❤️
✅🔔 Become a patron ► / zoranhorvat
✅🔔 Subscribe ► / @zoran-horvat
⭐ Learn more from video courses:
Beginning Object-oriented Programming with C# ► codinghelmet.com/go/beginning...
⭐ Collections and Generics in C# ► codinghelmet.com/go/collectio...
⭐ Making Your C# Code More Object-oriented ► codinghelmet.com/go/making-yo...
▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
⭐ CONNECT WITH ME 📱👨
🌐Become a patron ► / zoranhorvat
🌐Buy me a Coffee ► ko-fi.com/zoranhorvat
🗳 Pluralsight Courses ► codinghelmet.com/go/pluralsight
📸 Udemy Courses ► codinghelmet.com/go/udemy
📸 Join me on Twitter ► / zoranh75
🌐 Read my Articles ► codinghelmet.com/articles
📸 Join me on LinkedIn ► / zoran-horvat
▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
👨 About Me 👨
Hi, I’m Zoran, I have more than 20 years of experience as a software developer, architect, team lead, and more. I have been programming in C# since its inception in the early 2000s. Since 2017 I have started publishing professional video courses at Pluralsight and Udemy and by this point, there are over 100 hours of the highest-quality videos you can watch on those platforms. On my KZread channel, you can find shorter video forms focused on clarifying practical issues in coding, design, and architecture of .NET applications.❤️
▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
⚡️RIGHT NOTICE:
The Copyright Laws of the United States recognize a “fair use” of copyrighted content. Section 107 of the U.S. Copyright Act states: “Notwithstanding the provisions of sections 106 and 106A, the fair use of a copyrighted work, including such use by reproduction in copies or phono records or by any other means specified by that section, for purposes such as criticism, comment, news reporting, teaching (including multiple copies for classroom use), scholarship, or research, is not an infringement of copyright." This video and our youtube channel, in general, may contain certain copyrighted works that were not specifically authorised to be used by the copyright holder(s), but which we believe in good faith are protected by federal law and the Fair use doctrine for one or more of the reasons noted above.
⭐For copyright or any inquiries, please contact us at zh@codinghelmet.com
#dotnet #csharp #functionalprogramming

Пікірлер: 59

  • @Bankoru
    @Bankoru Жыл бұрын

    That "But I do, I'm sorry, it won't stop" is powerful

  • @arnofahrenkamp3491
    @arnofahrenkamp3491 Жыл бұрын

    Great! The only catch is that this is just an emulated DU and C# doesn't handle it with the confidence and elegance of F#. For those who don't have a love affair with F# like I do: In F#, the match expression would ensure that all cases of a DU are handled: As long as there is a match expression that does not handle all cases, it just won't compile. Anyway, thanks for this nice alternative!

  • @Bankoru

    @Bankoru

    Жыл бұрын

    You could enforce the switch expression to cause a compiler error though.

  • @konstsh2240

    @konstsh2240

    3 күн бұрын

    This is just one downside of current state of DU in C#, but I can find also a few benefits on C# side of DU-like classes: one can have a mutable DU-like data, extensible - you can have a new type with same cases + extra or with narrowed case set - the things that will unlikely be shipped to F# while the exaustive pattern matching almost for sure will be in C# at some moment

  • @10199able
    @10199able Жыл бұрын

    once you go functional, you'll never go back

  • @sandortardi7979

    @sandortardi7979

    Жыл бұрын

    Agree. Zoran has infected me with it for more than a year now:)

  • @bitmanagent67
    @bitmanagent6727 күн бұрын

    This is the problem I am trying to solve this week. Timely.

  • @codingbloke
    @codingbloke Жыл бұрын

    You are winning me over to functional programming. Very nice explanation, mixing both example, theory and exhortation.

  • @zoran-horvat

    @zoran-horvat

    Жыл бұрын

    As I said in the video, that is the future of C#. Observe what syntax was added to C# in the last couple of years - it was mostly functional elements.

  • @vulcanobyte
    @vulcanobyte Жыл бұрын

    Thanks Mr. Zoran Horvat

  • @EugeneS88-RU
    @EugeneS88-RU5 ай бұрын

    When video started I think about pure C union structure data(struct where all fields have one start address).but.. its so exotic😀

  • @nickbarton3191
    @nickbarton3191 Жыл бұрын

    So good, you're provoking me to up my game.

  • @zoran-horvat

    @zoran-horvat

    Жыл бұрын

    Go on and do so!

  • @yogabija
    @yogabija Жыл бұрын

    Thank you, Iliked the description and the way you present.

  • @Dr-Zed
    @Dr-Zed Жыл бұрын

    Great explanation as always!

  • @ThisIsSimonK
    @ThisIsSimonK Жыл бұрын

    Thank you for your hard work!

  • @moneymaker7307
    @moneymaker7307 Жыл бұрын

    Another banger of a videos. Fantastic! Fantastic!! videos. Me being a die hard OOP guy. I will argue the OOP implementation of this concept is much stronger. Why do you prefer this method over the double dispatch? I even feel like your implementation is quite similar to double dispatch in OOP but instead of using a reduce, you are using extension method. Another advantage of the OOP style implementation is that it would even work on graph object. meaning we can reduce a type with graph like property to any generic type. here is example of how I would implement this feature you are working on from concept I learned from OOP. public abstract class CitationSegment{ public string Text {get; set;} CitationSegment(string text) this.Text = text; public abstract T Reduce(ITransformer transformer); } public class BookAuthorSegment : CitationSegment{ public string AuthorId public string Name public override T Reduce(ITransformer transformer) => transformer.Transform(this); } public class BookTitleSegment: CitationSegment{ public string BookId public string Title public override T Reduce(ITransformer transformer) => transformer.Transform(this); } public interface ITransformer { T Transform(BookAuthorSegment segment); T Transform(BookTitleSegment segment); } public FormatWithBracket: ITransformer{ string Transform(BookAuthorSegment segment){ //write the transformation code -> BookAuthorSegment BookTitleSegment

  • @zoran-horvat

    @zoran-horvat

    Жыл бұрын

    The drawback of the OOP approach is that everything is forced to be an object, adding new types for even the simplest requirements. In FP many pieces of the complex functionality can be implemented as impromptu expressions and lambdas, making code overall simpler and shorter by a large margin.

  • @moneymaker7307

    @moneymaker7307

    Жыл бұрын

    ​@@zoran-horvat After watching the video again for the third time. what I am seeing is that the implementation I provided above and your implementation is almost the same. The functional approach you show in this video has the same drawback as the OOP approach. For you to add new functionality in your implementation, you will need to introduce an object that implements the IAuthorNameFormatter interface. I don't see how you could add new functionality using expressing only, and for you to introduce any functionality that format a list of author differently you will have to introduce an object that implement the IAuthorListFormatter, or maybe I thinking differently about the extension points of your application. One of the things I completely hate about the OOP style implementation is that we have to break encapsulation in other to work with the domain object. Looking at your implementation, it look like that problem can now be solved by using record type. Really love this video. It gave me a lot to think about.

  • @_NguyenManhToan_
    @_NguyenManhToan_ Жыл бұрын

    Thank you very much!❤❤❤❤❤

  • @dyslexicsteak897
    @dyslexicsteak897 Жыл бұрын

    Zorvan, you have a very soothing voice! If I may, I'd like to recommend The Rust Programming Language to you. It has first class support for both imperative and declarative paradigms, which allows you to pick and choose what suits you in a certain situation. Many patterns are welcomed and situationally finding the best match is highly encouraged rather than trying to apply a single pattern to a whole codebase. You'll be happy to see discriminated unions make an appearance as enums, along with very powerful pattern matching utilities, among other things. Hope to see you try it!

  • @obinnaokafor6252

    @obinnaokafor6252

    Жыл бұрын

    So he should drop C# for rust? lol

  • @dyslexicsteak897

    @dyslexicsteak897

    Жыл бұрын

    @@obinnaokafor6252 it's up to him what to do, 1, neither, or both. Personally though, I think the experience you get with Rust makes it difficult to use other languages after.

  • @obinnaokafor6252

    @obinnaokafor6252

    Жыл бұрын

    @@dyslexicsteak897 I believe he is getting amazing experience with C#.

  • @dyslexicsteak897

    @dyslexicsteak897

    Жыл бұрын

    @@obinnaokafor6252 This hack with records is no match for proper tagged unions. You can believe that but it's still wrong.

  • @adambickford8720
    @adambickford8720 Жыл бұрын

    Keep in mind that this is simpler, but more limited, than a traditional OOP solution. The hint is in having to know all the subtypes at compile time. In OOP you could (theoretically) get new instances of your objects at runtime from unknown 3rd parties and they will 'work' since it has all it needs to be 'pluggable'. (Spoiler: you'll almost certainly have to write some kind of 'plugin' architecture since OOP alone can't do it, but you still get all the OOP baggage) Of course, in practice, are you really doing that? Running code that hasn't been tested together is sketchy at best, running code you've never even seen feels... wonderfully naive.

  • @zoran-horvat

    @zoran-horvat

    Жыл бұрын

    The reason why functional programming is winning in the field of business applications is precisely that, once you model a concept and list all its variants, it is almost always final. Think of any business model you are developing now at your work. Speaking of any concrete element of that business, how likely it is that there will appear another variant of that element in the future? A new production material - once in a decade; new payment method - once every 20 years; new publishing medium - once in 20 years; new type of fuel - once in a decade. Just list what there is now and live happily with that! Nobody will come in a month and ask you to extend that. Endless extensibility of classes is the powerful tool we never utilized in everyday development!

  • @evanhowlett9873

    @evanhowlett9873

    Жыл бұрын

    I think another overlooked feature with compile-time variant handling is more correct code. You can't crash a system when you can't throw unforeseen data at it.

  • @zoran-horvat

    @zoran-horvat

    Жыл бұрын

    @@evanhowlett9873 That is actually the reason why we need syntactic support for discriminated unions, rather than using subpar solutions like records.

  • @Mig440

    @Mig440

    Жыл бұрын

    Fun fact, java already has a notion of discriminated union types with pattern matching to boot. It is called sealed classes (not to be confused with c# keyword sealed). So when you pattern match over a sealed hierarchy of classes/records, the compiler will let you know where to change your code if you add a new subtype in the future 😊

  • @milosmrdovic7233
    @milosmrdovic72333 ай бұрын

    Great video, Zoran! It really got me into thinking about an ideal approach for this particular problem. It's a classic objects vs data structures dilemma as the discriminated unions are nothing more than data structures with a few additional constraints and compile-time checks. Obviously, there are no silver bullets - each has its own strengths and weaknesses. With objects, it's easy to add new types but difficult to add new polymorphic behavior without changing all the derived classes. With data structures, it's the exact opposite: easy to add new functionality to the family of data structures, but difficult to add new data structures without changing the existing functions that operate on them. Having said that, I'd argue this particular example is better suited for objects and OO in general because the number of citation segment types will likely grow as the domain evolves (for instance, it makes sense to add a publisher segment, academic institution segment, journal segment, etc) while the number of functions will likely remain the same (citation segments are mostly for display purposes). I personally don't value syntactic sugar over a well-structured and expressive code so closing the consumption code against adding new segment types is what seems like a better choice. The need to separate the UI code from the domain is a slight inconvenience, but nothing that a little bit of reflection can't fix. Keeping the same CitationSegment domain classes, I'd implement something like this in the UI layer: public interface ICitationSegmentHtmlConverter { HtmlString ToHtml(); } public class BookAuthorConverter(BookAuthorSegment author) : ICitationSegmentHtmlConverter { public HtmlString ToHtml() => new HtmlString($"{author.Name}"); } public class BookTitleConverter(BookTitleSegment title) : ICitationSegmentHtmlConverter { public HtmlString ToHtml() => new HtmlString($"{title.Name}"); } public class DefaultConverter(CitationSegment segment). : ICitationSegmentHtmlConverter { public HtmlString ToHtml() => new HtmlString($"{segment.Text}"); } public static class CitationSegmentExtensions { public static HtmlString ToHtml(this CitationSegment segment) { Type converter = segment.GetMatchingConverter(); return converter.Instanciate(segment).ToHtml(); } private static Type GetMatchingConverter(this CitationSegment segment) { return typeof(ICitationSegmentHtmlConverter).GetImplementers() .FirstOrDefault(f => f.ConstructorParameterMatches(segment.GetType())) ?? typeof(DefaultConverter); } } public static class TypeExtensions { public static IEnumerable GetImplementers(this Type interfaceType) => Assembly.GetExecutingAssembly().GetTypes().Where(t => interfaceType.IsAssignableFrom(t) && !t.IsInterface); public static bool ConstructorParameterMatches(this Type type, Type argumentType) => type.GetConstructors().Any(c => c.GetParameters().Count() == 1 && c.GetParameters().First().ParameterType == argumentType); public static T Instanciate(this Type type, params object[] constructorArgs) => (T)Activator.CreateInstance(type, constructorArgs); } No ugly if statements, no cryptic switch statements, no syntax ninjitsu, the UI consumption code becomes trivial, classes are small & boring & easy to understand and test, and most importantly no need to change anything when a new segment gets introduced. If you don't add a new ICitationSegmentHtmlConverter, the default one will do just fine. If the performance is a concern, converters can be cached. OCP and SRP principles in action. The drawback: you need to use reflection to find the right converter. A rather small price to pay for all the benefits you get. Cheers!

  • @zoran-horvat

    @zoran-horvat

    3 ай бұрын

    This is a nice analysis. Thank you for finding time to put this together.

  • @ghevisartor6005

    @ghevisartor6005

    3 ай бұрын

    sorry this is probably going over my head, I'm used to Blazor and not normal razor pages. Why wouldn't i want to put this logic in a razor component with a switch on the segment base type and then all the derived ones with their html string?

  • @andrewthompson8448
    @andrewthompson84485 ай бұрын

    This is all great stuff. I would personally make all union members 'sealed' as well - So as to strongly discourage further derived types, and to make the intention of DU's clear.

  • @zoran-horvat

    @zoran-horvat

    5 ай бұрын

    That makes sense. Actually, I got into the habit of doing it that way later, after publishing this video.

  • @Naton
    @Naton Жыл бұрын

    it's beautiful !!!

  • @MagicNumberArg
    @MagicNumberArg Жыл бұрын

    I'm already begging, please, give me Sum Types, or at-least Discriminated Unions!

  • @phyberoptyk5619
    @phyberoptyk5619 Жыл бұрын

    The very first library I add to a new project is OneOf, which emulates a DU quite elegantly until we have native support.

  • @zoran-horvat

    @zoran-horvat

    Жыл бұрын

    Not really elegant in my opinion. Extremely verbose notation, and also quite different from what I expect the new syntax to look like, once it comes.

  • @phyberoptyk5619

    @phyberoptyk5619

    Жыл бұрын

    @@zoran-horvat I am anxiously awaiting whatever Mads is cooking up.

  • @5cover
    @5cover7 ай бұрын

    Actually, there is a way to restrict inheritance to a closed set of classes (same goes for records which are essentially classes except for "record struct"). Give the base class a private parameterless constructor. Define all children classes as nested inside the base class, so they have access to this constructor. Now trying to derive the base from outside it will result in a compiler error.

  • @vatyunga

    @vatyunga

    Ай бұрын

    Yes but it doesn't give you compile time warnings about missing cases in switch expressions.

  • @nickcorrado5105
    @nickcorrado5105 Жыл бұрын

    You say around 7:30 that the greatest enemy of DUs is adding a type. I don't think that's true. It's actually an advantage of DUs, *properly implemented*: when you add a type, every single place where you aren't handling that type throws a compile error. Given that the code can't write itself, I think that's the best user experience you could ask for!

  • @zoran-horvat

    @zoran-horvat

    Жыл бұрын

    Actually, it is the other way around. One programmer writes the DU, and another writes a function that consumes it. The person adding a type to a DU will break somebody else's software. Conversely, adding a function on a DU will not affect anyone. That is the direct opposite to what we have in OO, where adding a type to a hierarchy doesn't affect anyone, while adding a method to the base type will break somebody else's code.

  • @nickcorrado5105

    @nickcorrado5105

    Жыл бұрын

    I see what you mean now!

  • @ghevisartor6242

    @ghevisartor6242

    11 ай бұрын

    @@zoran-horvat sorry maybe i dont get it, if im the only developer and i add the PublisherSegment(String Name, Guid PublisherId) etc what's the issue? having for example the switch throw a not supported exception? If you make a nuget package or an api for someone else i can see it be a problem but if you manage all the stack is that an issue? so instead of discriminated unions what would you do if you think requirements will change?

  • @zoran-horvat

    @zoran-horvat

    11 ай бұрын

    @@ghevisartor6242 That is the classical division. Which is more likely to happen over a longer period of time: Adding more types, or adding more functions? In the former case, inheritance is a better choice; in the latter, subtyping with extension methods. Anything in between is the zone of pain.

  • @abj136
    @abj136 Жыл бұрын

    You said after watching this video I would be begging for C# to have extra syntax to support discriminated unions. But you demonstrated discrirminated unions, so I don’t know what you think we’re missisng. Maybe we need to see what F# does as an exact analagy to see how it’s cleaner.

  • @zoran-horvat

    @zoran-horvat

    Жыл бұрын

    The implementation from the demo is unsafe. You can extend the CitationSegment record elsewhere, and that would affect the mapping expression in the UI or anywhere else in the application, even to the point of breaking them and causing an exception. Such extensions are impossible with proper discriminated unions - they fail at compile time - and that is what C# is currently lacking.

  • @secury
    @secury10 ай бұрын

    Thank you so much for the video. I am writing a plenty of code on frontend and was able to use this technique right away at my code. There can be complex rendering logic based on business requirements and using set of distriminitated states enhance readebility, specifiacally I find it easier scan the sequence of states in one column and then focus on one area then hold the mental web of ifs and elses clauses spread in the code. There is one thing that I want to ask if I understand it correctly. This technique could look as breaking one of the OOP principles that says that consumer should depedent on the interface rather than concrete implementation. And now, If I am manually casting and inspecting the interface into all possible types that kinda defeat the point having the interface in the first place right? But that is coming from OOP perspective. From the FP perspective this is just implementation trick and I am not breaking any guidelines? Thanks again for your content. Looking forward for another video.

  • @zoran-horvat

    @zoran-horvat

    10 ай бұрын

    You are right in your question. The trick is that this kind of types is not object-oriented, and hence that reasoning does not apply. Discriminated unions are part of functional type design. I would encourage you to read about * and + types and their use in functional programming. While it is true that these types cause issues when we add more variants of them, they also have a great power in that they separate implementation of an unrelated feature from the definition of the core type. There is another benefit, that the entire feature is always located in only one file, which helps improve readability a lot.

  • @josebarria3233
    @josebarria3233 Жыл бұрын

    Is it possible to do the same using extension methods on the UI?

  • @zoran-horvat

    @zoran-horvat

    Жыл бұрын

    Yes, extension methods with pattern matching are a common way to define behavior on a discriminated union.

  • @Bankoru
    @Bankoru Жыл бұрын

    DUs always seemed to me like enums with extra data

  • @zoran-horvat

    @zoran-horvat

    Жыл бұрын

    In Rust, it is indeed declared as an enum with fields! Here you can see an example of what it looks like: doc.rust-lang.org/rust-by-example/custom_types/enum.html

  • @wuketuke6601
    @wuketuke66016 ай бұрын

    The way you showed me discriminated Unions, it seems like its an enum with extra data attached to them

  • @zoran-horvat

    @zoran-horvat

    6 ай бұрын

    Don't forget the methods, too

  • @wuketuke6601

    @wuketuke6601

    6 ай бұрын

    @@zoran-horvat Yes youre right

  • @lukeyd13
    @lukeyd13 Жыл бұрын

    Discriminated unions are orthogonal to functional programming

Келесі