Build Your Own Option Type in C# and Use It Like a Pro

แชร์
ฝัง
  • เผยแพร่เมื่อ 27 ก.ค. 2024
  • Become a patron and get access to source code and exclusive live streams: / build-your-own-c-81382247
    In this video, you will learn how to design a powerful and flexible implementation of the Option type, and then use it to build equally powerful domain models. It is not necessary to use third-party libraries that include Option implementation. It is not the Option type a programmer needs - such a type can be coded in minutes. What programmers truly need is the backing knowledge of how and when to use optional objects.
    This video combines the two questions: defining the Option type and designing a domain model that incorporates it, into one unified demonstration. You will learn how Optional objects can be used to bridge the gap between potentially missing objects at run time, and functions that always require an object to produce a result. It is that powerful Map-Reduce combination that will make your domain models expressive on the one hand, but also extensible and composable on the other. What else could one wish for?
    The complete source code from this demonstration is available in GitHub repository: github.com/zoran-horvat/optional
    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...
    Other courses at Pluralsight ► codinghelmet.com/go/pluralsight
    Other courses at Udemy ► codinghelmet.com/go/udemy
    Other videos on this channel you may be interested in watching:
    Using GitHub Copilot to Write Complex Code | Step-by-step Tutorial ► • Using GitHub Copilot t...
    Coding with GitHub Copilot - Beginner to Master | VS Code Demo ► • A Comprehensive Guide ...
    What is Covariance and Contravariance in C# ► • What is Covariance and...
    How to Initialize a Clean ASP.NET Core Project with Entity Framework Core and Identity ► • How to Initialize a Cl...
    The Null Conundrum: A Guide to Optional Objects in C# ► • How to Avoid Null Refe...
    #oop #dotnet #functionalprogramming
  • วิทยาศาสตร์และเทคโนโลยี

ความคิดเห็น • 80

  • @zoran-horvat
    @zoran-horvat  ปีที่แล้ว +4

    Become a patron and get access to source code and exclusive live streams: www.patreon.com/posts/build-your-own-c-81382247

  • @7th_CAV_Trooper
    @7th_CAV_Trooper 4 หลายเดือนก่อน +11

    This is the best C# channel on youtube. Zoran is a master teacher.

  • @marklord7614
    @marklord7614 ปีที่แล้ว +15

    The C# team has gone through great pains to make dealing with nulls easier, but obviously, things could be better. I would like to hear more from the C# community on the need for the Option type in C#. Zoran, it is a crime that you don't have more subscribers.

    • @zoran-horvat
      @zoran-horvat  ปีที่แล้ว +7

      They will come. I am not worried about that, but thank you!

  • @MuhammadAhmed-pd8zu
    @MuhammadAhmed-pd8zu 11 หลายเดือนก่อน +1

    Man, you are truly awesome. I can say that I am only enjoying this type of content and knowledge amongst the whole entire crowd !! Thank you so much .

  • @azdanov
    @azdanov ปีที่แล้ว +6

    After watching your videos I feel like I'm reaching programming enlightenment. Thank you a lot!

    • @zoran-horvat
      @zoran-horvat  ปีที่แล้ว +2

      Thank you for kind words.

    • @azdanov
      @azdanov ปีที่แล้ว

      What was the optimization about for using a struct instead of class in the GitHub repo? And in general for someone coming from Java this struct/class is confusing.

    • @tarsala1995
      @tarsala1995 ปีที่แล้ว +1

      @@azdanov structs usually live on the stack, which means that there is no garbage collection needed, this might improve performance significantly (but also requires more understanding of memory management, so it might backfire at you if structs are incorrectly applied)

    • @okcharles7
      @okcharles7 7 หลายเดือนก่อน

      @@tarsala1995 Structs can live on the heap, too if it is an instance member of a class. The performance benefits come from less steps of GC; if it stays in the stack, no GC is required to clean it, but if in the heap, it is cleaned up when its nesting object cleaned, where is the case, reference type instances need an extra step of GC; one for its nesting object and another for itself.

    • @tarsala1995
      @tarsala1995 7 หลายเดือนก่อน

      @@okcharles7 Yup, or for array of structs as well. That's why I put "usually"

  • @LucasSouza1
    @LucasSouza1 ปีที่แล้ว +6

    That's a masterclass. Thanks for the content.

  • @superpcstation
    @superpcstation ปีที่แล้ว +1

    This is very helpful. Thanks Zoran!

  • @Fl00wt13
    @Fl00wt13 3 หลายเดือนก่อน +2

    Definitely Content

  • @orthodox-4-ever
    @orthodox-4-ever ปีที่แล้ว +9

    Earthquake part was so funny 😀

    • @zoran-horvat
      @zoran-horvat  ปีที่แล้ว +5

      Not for everyone I guess.

  • @Pasukaru0
    @Pasukaru0 ปีที่แล้ว +2

    We use a similar approach like this, but we call it Result. It either has a value, or an error. That allows us to do error handling very efficiently, without having to rely on expensive catch clauses.

    • @zoran-horvat
      @zoran-horvat  ปีที่แล้ว +4

      Result is a different monad, called Either in functional languages. But the principle is the same.

  • @mirekmiedziarek51
    @mirekmiedziarek51 ปีที่แล้ว +1

    Awesome content Sir, Keep doing it ! Cheers!

  • @peep39
    @peep39 3 หลายเดือนก่อน +1

    I love F#, and I try to use it where I can, but I work in a C# shop so my hands are somewhat tied. I would absolutely love to see union types (they make a lot of problems so simple) in C# but I fear we never will. BTW I am a 20 year .NET veteran and I am very much enjoying your videos.

    • @zoran-horvat
      @zoran-horvat  3 หลายเดือนก่อน +1

      I am sharing your feelings.

    • @fsharpfan
      @fsharpfan 2 หลายเดือนก่อน

      Many new features in C# are functional. Using higher order functions in C# is a bit pain because you need to define all the parameter types. In F# you just give a name of the param and the type is inferred. Partial function application will never be in C#. I also write all new code in F# when I can. My client uses C#, but I introduce functional style code when I can.

  • @Brendan2Alexander
    @Brendan2Alexander ปีที่แล้ว +2

    Also known as the Maybe type (as learned from Vladimir Khorikov)

  • @tsvikanahir9428
    @tsvikanahir9428 ปีที่แล้ว +1

    Excellent tutorial! This can so much stability to the code and of course save the need for endless null checks. Thanks for sharing.
    Wonder if you could add an example of the mentioned railway use.

    • @zoran-horvat
      @zoran-horvat  ปีที่แล้ว +3

      I plan to do a video on railway-oriented programming alone, some time in the future.

  • @vamvdotnet
    @vamvdotnet ปีที่แล้ว +1

    It's a very difficult topic to ingest - and mostly apply - but I'll get there eventually.

  • @thomasschroter3802
    @thomasschroter3802 ปีที่แล้ว +2

    I recently came across Zoran Horvat's TH-cam channel, and I'm very pleased about his excellent reflections over C# programming language. I remembered facing similar problems in most projects.
    In C# the Option-type-pattern contains a value as reference to the wrapped object that can be regarded as the state and publishes mapping function(s) to alter/cope with that state. What I dislike is the consumption of heap that this wrapping takes. Each call to mapping will create a new instance on the heap and thus putting load on the GC.
    My idea is, to take the internal state and sacrifice encapsulation and immutability by simply applying a map-function on any object. Now it is possible to reduce most code of the option-type, into compact extension functions that allow pretty much the same functionality without continuous allocation of heap memory.
    The demo uses Zoran's example code. I replaced the Option-type with a single functional extensions to make the code run. The output is the same as in the original example.
    github.com/tomschrot/Alternative-Functional-Option-Pattern

  • @HexNull
    @HexNull ปีที่แล้ว +5

    Really Great video! The only problem with using option is that it's not necessary to use. So while working with let's day different remote team, they just might completely ignore it :(
    I wonder if there is some way to enforce it

    • @zoran-horvat
      @zoran-horvat  ปีที่แล้ว +13

      Oh yes, there is the way to enforce it - let the C# language team include it into the language!
      Just look at how Rust did it. Everything there, and I do mean everything, is built on a simple and definite rule: A function either returns an object, if it can promise to always create it; or it returns an Option, if there are situations when it cannot create the object, given the state and inputs; or it returns a Result, if there are error conditions that can prevent it from producing a result.
      I am sure that nobody has ever raised a question of introducing nulls into Rust, while there are so many complaints about introducing optionals and results into C#. The problem is only in the level of knowledge among programmers, I can testify that.

  • @tarsala1995
    @tarsala1995 ปีที่แล้ว +2

    Those heaven signs about not talking monads :D

  • @DmitriNesteruk
    @DmitriNesteruk 2 หลายเดือนก่อน

    Why have ToOption() when you can just have an implicit conversion operator from T to Option? That way you can transparently declare parameters of type Option and pass ordinary objects into them. Also, why not have `public static Option None = new();` as a single instance? There is no real need to have it as a method. In fact, you could sacrifice type information and introduce a non-generic (Value)Option just to have a `None` member so it could be referred to without extra type information.

  • @d2pgoon86
    @d2pgoon86 ปีที่แล้ว +1

    Excellent demonstration, as always, Zoran!
    I was able to spot the whitespace bug, but can you spot the bug lurking in the `Equals(Option? other)` implementation 😉? The fact that your seemingly trivial `Equals` implementation has a bug I think further illustrates just how error prone nulls really are! There is just so much branching that results in use of nulls.

    • @zoran-horvat
      @zoran-horvat  ปีที่แล้ว +1

      That is possible! I was aiming at struct implementation of Option from the very beginning, the one in the repo, and Equals implementation in a struct is entirely different.

    • @d2pgoon86
      @d2pgoon86 ปีที่แล้ว +1

      And therefore, much easier to implement correctly because it saves you the need to handle an additional null, in the case of the input argument, since it is a struct.
      I think it's obvious that we need Option from the C# team. Null is so antiquated 😄

    • @zoran-horvat
      @zoran-horvat  ปีที่แล้ว +4

      @@d2pgoon86 You've made a few good points here. But before asking the C# team for Option and Result, we need a dramatic improvement in understanding of how to use them in code.
      The large body of C# developers doing business today are the product of programming books that repeat, over and over, the same text that was in use in 1980s.

  • @okcharles7
    @okcharles7 7 หลายเดือนก่อน

    At first, thanks a lot for this great tutorial and linked ones as well.
    However, I am having trouble understanding about the Map method.
    It looks like converting an option to another, which, my preferably, can be written in nullable context as:
    TR? Convert(Func how)
    In many of dotnet libraries, converting is ruled by IConverter, which can be adopted only when it is necessary but all Option instances are always ready to be converted.
    That is the point where I can't proceed further.

    • @zoran-horvat
      @zoran-horvat  7 หลายเดือนก่อน

      This is not about conversion, but about applying a function to a possibly missing object. The result of such a function could possibly be missing itself.
      Option is a monad and it follows the monadic rules. You can learn more about monads on the Internet.

  • @coolfyb
    @coolfyb 10 หลายเดือนก่อน +1

    eXcellent Zoran.

  • @abd.alharbi
    @abd.alharbi 4 หลายเดือนก่อน

    Hi @Zoran. Thanks for the great content.
    Suppose I have an `Option` object that - in case the `_content` is null - I'd like to reduce it to a default value at some place in my code and throw an `ReferenceNullException` at another. This use-case is not covered in this video but here's my thought process and kindly inform me if I went wrong anywhere. Define a `ThrowIfNull()` method and call instead of a `Reduce()`. But I'm not sure if it should be an extension method or it should be defined on the `Option` class. I know it won't have access to the private member `_content` to check if it's null if the method was an extension of the `Option` and I'm not sure if there's a work-around.

    • @zoran-horvat
      @zoran-horvat  4 หลายเดือนก่อน

      You should not store null in an option. Actually, you should declare an option of a non-nullable content type to begin with. Nullable content type opens up a whole lot of nasty corner cases.

  • @kasozivincent8685
    @kasozivincent8685 3 หลายเดือนก่อน

    I think the most important method would be bind. That would turn Option into a monad

    • @fsharpfan
      @fsharpfan 2 หลายเดือนก่อน

      Zoran has a MapOption function which does exactly the sama as bind function, so Option is a monad

  • @ivancavlek4755
    @ivancavlek4755 ปีที่แล้ว

    I started getting interested in Monads after a blog from Mark Seamann. It looks very appealing.
    But how would I use it when binding to a (NoSql, relational) database? When I send/receive data through a web service or if I construct an API?

    • @zoran-horvat
      @zoran-horvat  ปีที่แล้ว +2

      These are good questions. Option, Either and similar types are modeling tools, while data transfer and persistence are more of a serialization question. You would invariantly transform optional objects before sending and upon receiving, as well as before saving to database or on materialization. Options do not exist in non-object representations, that is important to notice.
      In both cases, I use null as a substitute for an option. In JSON, you can choose to leave a field empty or to skip it altogether. In SQL, keep it in a nullable column. When using EF Core, I add a private nullable property which is then mapped to a nullable column in the database table, while the public Option property is ignored in persistence model and only used in the application.
      You can then choose to either keep the value in the private nullable property and construct the Option on every call to the property getter, or vice versa - to materialize the Option and calculate its nullable counterpart when saving the object to the database. Looking from the outside, neither EF Core nor the rest of the domain model will see this happening.

  • @bojanzdravkovic8566
    @bojanzdravkovic8566 ปีที่แล้ว

    Just a thought...could this be baked into Nullable ?
    So we'd get:
    string? data;
    data.Map(...).Reduce(...)

    • @zoran-horvat
      @zoran-horvat  ปีที่แล้ว +1

      Nullable type only supports a value type as its content. That is the sharp limitation. Otherwise, it could easily be tweaked via extension methods to support map-reduce principle, a.k.a. railway-oriented programming.

  • @Bankoru
    @Bankoru ปีที่แล้ว +3

    The best monad

  • @Sc0nes
    @Sc0nes ปีที่แล้ว +1

    Since the null coalescing operator in the Reduce function will only evaluate the "orElse" if the content is null. What's the benefit of the lazy implementation? (Around 14min)
    Btw thanks for all the great videos. I hope your channel will continue to grow!

    • @zoran-horvat
      @zoran-horvat  ปีที่แล้ว +2

      Lazy variant is useful in cases when evaluating the alternative is too costly, given that it might not be used. The demo may not communicate this idea all too well...
      On the other hand, the class's public interface does not expose that it is the null coalescing operator that does the work. It could be dangerous to leak that decision publicly.

    • @ldeerosel
      @ldeerosel 9 หลายเดือนก่อน

      Just think of 'orElse' as a function that executes a query. It's expensive

  • @barionlp
    @barionlp 7 หลายเดือนก่อน

    I started rewriting a system that heavily works with strings and options to use spans but as spans are ref structs they can't be referenced in lambdas making the option type useless in this scenario. do you have any idea on how to still benefit from options in this case?

    • @zoran-horvat
      @zoran-horvat  7 หลายเดือนก่อน +1

      Spans are an extreme means of optimization of CPU-bound operations. They naturally don't come into the same context with options, lambdas, polymorphic calls, etc.
      It is natural to use procedural programming techniques on spans.

    • @barionlp
      @barionlp 7 หลายเดือนก่อน

      @@zoran-horvat It just adds so much complexity to the system but i guess thats the tradeoff

  • @RostislavNikitinHuman
    @RostislavNikitinHuman ปีที่แล้ว

    Hello. Thank you for the video.
    I think I found a mistake in your code at [11:42]. If _content is null and other._content is null (both are nulls) your code will return false. But it should return true.

    • @zoran-horvat
      @zoran-horvat  ปีที่แล้ว +1

      Is that about the Equals operator? I think you're right. There should be another hand in the test which covers equality when this._content is null.

  • @jonnroc
    @jonnroc ปีที่แล้ว +1

    Zoran, how may I hire you for your services? Would you be willing to offer paid PR reviews? I'm building several code libraries, and would very much like to consult with you.

  • @abhijeethaval
    @abhijeethaval ปีที่แล้ว

    How to map optional to the database using EF Core?

    • @zoran-horvat
      @zoran-horvat  ปีที่แล้ว

      EF Core still doesn't support the mapping that is required by the Option type. For that reason, I add a private nullable field for persistence reasons and calculate the public Option from that field. Hopefully, EF Core will improve mappings to support type transforms as well, not just value transforms.

  • @fluffydoggo
    @fluffydoggo 5 หลายเดือนก่อน

    Why are you declaring Option as a class instead of a struct? Ive worked with an Option object that is a struct and can wrap anything. My favorite extension I've added is called ToNullable and looks like this
    public static T? ToNullable(this Option optional) where T : struct => optional.IsSpecified ? optional.Value : null;
    I'm actually curious on your thoughts for 'IsSpecified' and 'Value' properties on an Option.
    Just want to say thanks for the videos I love them

    • @zoran-horvat
      @zoran-horvat  5 หลายเดือนก่อน

      I have shown the principal idea in this video, which is hard enough for many programmers anyway to add even more into it. I did demonstrate a struct implementation elsewhere and it had an unpleasant twist. Since structs cannot be polymorphic, a single struct must do it all. That makes nullable reference a good choice for the private representation, but that in turn requires an entirely new struct to support value typed, because it would wrap a Nullable struct that handles a missing value! The implementation is twice as long when using a struct.
      Regarding accessors to the content, I am strongly against them. The idea of options is to use bindings on them, not to turn them into another null, consumed by ifs.

    • @fluffydoggo
      @fluffydoggo 5 หลายเดือนก่อน

      @@zoran-horvat my biggest problem with that is that I need to map an (for example) Option to Option. That would result in something like this:
      public static T? ToNullable(this Option optional) where T : struct => optional.Map(x => x).Reduce(null);
      That lamda call just seems weird in my opinion.

    • @zoran-horvat
      @zoran-horvat  5 หลายเดือนก่อน

      @@fluffydoggo The whole idea of optional objects is that you don't use nullable types.

  • @hopext
    @hopext ปีที่แล้ว +2

    when you asked microsoft if they will include option in the future, and answered you that they dont want another null. do you think that answer is a proof of their ignorance about the power of option monad ?

    • @zoran-horvat
      @zoran-horvat  ปีที่แล้ว +11

      That question is not so easy to answer. I am sure that the C# team is not ignorant about Option and Either types - quite the contrary, I am sure that their comprehension of the problem is way better than mine.
      The language team is dealing with the problem I, as a programmer, do not have. Their goal is to create a language which is both versatile and safe to use. The latter is often neglected, but there is evidence that the language team was driven by safety concerns more often than not in the past, when choosing whether to add a feature to the language or not.
      The problem, as I see it, is that optional objects are just not understood by majority of .NET programmers. That is a sad fact, but I can bet money when I say that 90%+ of all C# programmers don't have a clue what an optional object is, let alone how to use it in modeling. Give them an Option now and you will have that dreaded "another null". I believe that is what Mads said a few years ago, though I never asked him to confirm.
      And here is my conclusion. I don't blame the language team for not including Option into C# yet. I think they are right in that respect, given the state of knowledge among C# programmers. What I do blame them for is not investing enough to teach the programmers and make them more aware of the new dominating practices.
      Compare that situation to a newer language, Rust. In Rust, every function that might not be able to produce the result is mandated to return an Option. Rust programmers were taught to do it that way from day one. Nobody in the Rust community would stand up today and say hey, why don't we have nullable reference types?
      I hope that C# language will evolve in the direction where nullable reference types remain low-level modeling tool, useful in serialization/deserialization, persistence, memory management etc. but not in domain modeling.

  • @mrteco4236
    @mrteco4236 5 หลายเดือนก่อน

    Doesn't C# have nullable types? Like string? foo = "";

    • @zoran-horvat
      @zoran-horvat  5 หลายเดือนก่อน

      It does, but nullable types only support a fraction of what we routinely do with optional objects.

  • @vulcanobyte
    @vulcanobyte ปีที่แล้ว +1

    Wouldn't you like to collaborate with freecodecamp? With a bootcamp I'm sure you would reach a million students because you inspire when you teach C#, greetings Mr. Horvat

  • @ahjsbkdjhavkcjhvac
    @ahjsbkdjhavkcjhvac 4 หลายเดือนก่อน

    3:00 the universe sent you a sign to not talk about monads!!! because no one likes monads! /jk ahhaha

    • @zoran-horvat
      @zoran-horvat  4 หลายเดือนก่อน

      I believe so :)

  •  6 หลายเดือนก่อน

    Whoever designed System.CommandLine polluted the namespace by using Option to represent a command line option. What a waste.

    • @zoran-horvat
      @zoran-horvat  6 หลายเดือนก่อน +1

      It amused me to think that optional objects were given the name through the same process as flags, i.e. options in all other libraries and classes that use them. The term is quite overloaded by now.
      On the other hand, there is that other name - Maybe - which wasn't adopted in OCaml, and hence not in F# either. I never truly liked that informative naming in programming.

  • @user-tk2jy8xr8b
    @user-tk2jy8xr8b ปีที่แล้ว

    Mmm, Some(null) == None() violates the functor invariant fmap g . fmap f == fmap (g.f):
    string? F(string? s) => s == "" ? null : s;
    string? G(string? s) => s is null ? "" : s;
    Some("").Map(v => G(F(v))) != Some("").Map(F).Map(G)

    • @zoran-horvat
      @zoran-horvat  ปีที่แล้ว

      Some constructor is defined to receive a non-nullable reference, so Some(null) is invalid code.

    • @user-tk2jy8xr8b
      @user-tk2jy8xr8b ปีที่แล้ว

      ​@@zoran-horvat ok, what error will the code `struct S { public string P { get; set; } }` and `Some(new S().P)` fail to compile with?

    • @zoran-horvat
      @zoran-horvat  ปีที่แล้ว

      @@user-tk2jy8xr8b What error do you want to have? What is your point?
      I mean, you have applied one out of a dozen possible methods to sneak a null into a function. That doesn't make your code any less invalid than it was.

    • @user-tk2jy8xr8b
      @user-tk2jy8xr8b ปีที่แล้ว

      @@zoran-horvat any error making the code invalid and, therefore, failing compilation

    • @zoran-horvat
      @zoran-horvat  ปีที่แล้ว

      @@user-tk2jy8xr8b Why does your struct compile then? It is violating its own declaration by setting the non-nullable component to null. You didn't have to go so far as to assign a faulty component further to an Option in an attempt to prove any point - so, again, what is your point?