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!
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
it's just mental master bateion, tbh. would you like to work on an fsharp projeckt, on an haskel project or on a go project. You will be the tech lead and all projects will be very big and complex. I'd go with go, tbh.
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 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.
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!
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?
Exactly, I am confused about DU, I think it is an anti-pattern. Why would you want to use it when you have interfaces to simplify access to specific behavior like segment.ToHtml()? . Maybe DUs work well when there are a finite number of subtypes, while interfaces work well with infinite cases. But if you can use interfaces, i think you should use them.
@damianjoel5833 The citation segment is already in a partial razor view. It's a much cleaner place than having html all over c# files. If I want something more complex I'll just make another partial view rather than using c# files which are primarily meant for c# code. By keeping html in razor files the editor will have better support for all the html tags and it's a lot easier to change the layout if necessary. It also keeps the models/records unaware from the presentation logic which could as well have been a Maui app or something where there is no html.
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
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.
@@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.
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!
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.
@@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?
@@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.
I've been begging for discriminated unions since 2012, when I first glanced into F#. It really saddens me we didn't get them earlier in C#, but at least the .NET team confirmed they are coming. Fingers crossed for .NET 10?
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.
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.
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.
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.
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.
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!
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.
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 😊
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.
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.
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
@@Cool-Game-Dev Correction: Don't go a year back in time to tell me... BTW another video on my channel that is also a year old predicts discriminated unions as the first big feature to come into C#.
That "But I do, I'm sorry, it won't stop" is powerful
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!
You could enforce the switch expression to cause a compiler error though.
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
You are winning me over to functional programming. Very nice explanation, mixing both example, theory and exhortation.
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.
once you go functional, you'll never go back
Agree. Zoran has infected me with it for more than a year now:)
it's just mental master bateion, tbh. would you like to work on an fsharp projeckt, on an haskel project or on a go project. You will be the tech lead and all projects will be very big and complex. I'd go with go, tbh.
When video started I think about pure C union structure data(struct where all fields have one start address).but.. its so exotic😀
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!
So he should drop C# for rust? lol
@@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.
@@dyslexicsteak897 I believe he is getting amazing experience with C#.
@@obinnaokafor6252 This hack with records is no match for proper tagged unions. You can believe that but it's still wrong.
Thanks Mr. Zoran Horvat
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!
This is a nice analysis. Thank you for finding time to put this together.
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?
Exactly, I am confused about DU, I think it is an anti-pattern. Why would you want to use it when you have interfaces to simplify access to specific behavior like segment.ToHtml()? .
Maybe DUs work well when there are a finite number of subtypes, while interfaces work well with infinite cases. But if you can use interfaces, i think you should use them.
@damianjoel5833
The citation segment is already in a partial razor view.
It's a much cleaner place than having html all over c# files. If I want something more complex I'll just make another partial view rather than using c# files which are primarily meant for c# code.
By keeping html in razor files the editor will have better support for all the html tags and it's a lot easier to change the layout if necessary.
It also keeps the models/records unaware from the presentation logic which could as well have been a Maui app or something where there is no html.
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
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.
@@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.
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!
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.
I see what you mean now!
@@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?
@@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.
The very first library I add to a new project is OneOf, which emulates a DU quite elegantly until we have native support.
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.
@@zoran-horvat I am anxiously awaiting whatever Mads is cooking up.
I've been begging for discriminated unions since 2012, when I first glanced into F#. It really saddens me we didn't get them earlier in C#, but at least the .NET team confirmed they are coming. Fingers crossed for .NET 10?
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.
So good, you're provoking me to up my game.
Go on and do so!
This is the problem I am trying to solve this week. Timely.
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.
That makes sense. Actually, I got into the habit of doing it that way later, after publishing this video.
Is it possible to do the same using extension methods on the UI?
Yes, extension methods with pattern matching are a common way to define behavior on a discriminated union.
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.
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.
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.
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!
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.
@@lethicanthus That is actually the reason why we need syntactic support for discriminated unions, rather than using subpar solutions like records.
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 😊
Thank you, Iliked the description and the way you present.
Thank you very much!❤❤❤❤❤
Great explanation as always!
I'm already begging, please, give me Sum Types, or at-least Discriminated Unions!
Thank you for your hard work!
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.
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.
DUs always seemed to me like enums with extra data
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
The way you showed me discriminated Unions, it seems like its an enum with extra data attached to them
Don't forget the methods, too
@@zoran-horvat Yes youre right
it's beautiful !!!
Discriminated unions are orthogonal to functional programming
Don’t tell him guys
@@Cool-Game-Dev Correction: Don't go a year back in time to tell me... BTW another video on my channel that is also a year old predicts discriminated unions as the first big feature to come into C#.
@@zoran-horvat well I personally can’t wait till they release, I’m very excited