Once again thank you Zoran. This discussion of records, domain modelling and FD has stimulated the brain over breakfast and has motivated me to reconsider my approach to solving tricky situations in a more elegant way.
First off: excellent video as always Zoran. Secondly: you can achieve even more explicit controls by using immutable record structs with private property setters to enforce creation through factory methods. This also enforces that discipline to use factory methods since you can't create them without it. If you need the with {...} functionality then just bump the properties to init-only. I've started taking this approach much more frequently as it also fixes the nullability issues with regular records, since they are really just classes with a fake moustache. You run into issues where the underlying properties may be objects that are returned by reference and are therefore mutable. If it's structs all the way down then they're explicitly by-val and can't mutate without reflection.
@@alexlawrence1337 I see what you mean. However, I tried to show vanilla records, in their natural state of one-liners, so that you can judge how far you should go.
@@zoran-horvat and there is definitely elegance in the brevity and simplicity of that approach. My only apprehension is I know that some developers love to abuse the default keyword, which skips constructors entirely and would initialise a record as null. This happens a lot when records are fields of API requests and aren't appropriated tagged as required, or when some developers see the warning that a field isn't initialized on construction and so they panic and initialise it as default instead of the empty factory method. It's more so an issue when the record you're creating isn't a dto or poco, but rather when you're implementing some other functional structure such as a monad. I do not like my monads to be null ever! Nor do I like dealing with nullable reference types when I don't have to, as nullable typed become almost poisonous, requiring null guards everywhere they are passed. It does also take a keen mind to write those structs in such a way that default becomes a valid state, as any structs within your struct are created with its properties set to 0.
As much as I love the overall advice on designing with records, if I saw a pull request with that mess of ternaries traveling off the screen in VideoClip.cs:24-26, I would send it back to the dev. Breaking that down into much more readable conditional blocks makes the code far more maintainable and understandable at a glance, and ultimately still compiles to the same IL. "Compact" or "concise" does not always equal "good" in coding. You did make it overall easier to read, but not that part.
@@lightandtheheat There is truth in that. I wanted to show the alternative to switch expressions where they do not support a particular dynamic pattern. In the end, this demo may serve as a motivation to extend switch expression syntax.
@@ghevisartor6005 It would look way less intimidating if I introduced helper methods to give names to patterns and corresponding expressions, such as WhenNotOverlapping, WhenInside, etc.
I havent watched the video but i did watch your functional c# course on pluralsight. Tried the records and tricks from there. Now i use them a loooot more. Was an awesome course btw.
Coming from F# land, this is so promising. Far from ideal, but promising! Waiting paitently for C# to adopt the rest of the magic of F#'s workflow! Btw, great video. You definitly got my sub!
While it obviously would be even nicer if one could disallow that it's not something that's that important imho. If you can't even trust your coworkers with this then you have bigger issues in your team. In the end it's about trade-offs and you gain so much and only lose a tiny bit.
@@Rick104547 That was my point, too. C# makes it super-easy this way. It comes with a drawback that we must be disciplined when coding. But we must be disciplined on a hundred other things in functional programming, so why fear it? There are easier ways to cause trouble than calling a generated constructor.
Records are a abstraction (less distraction) of immutable implementation in classes. While they speed up programming they have overlapping behavior that is not always utilized, you mentioned the public constructor. Personally i find nesting types a great way of encapsulating behavior, reducing the surface area of bugs while also helping other developers contextualize the types in meaningful way. I also find it more comprehensible if the 'main' type owns the behavior instead of exrensionmethods. Great video, please make more 🧑🔧
Thank you for this video. I avoided using of C# records because don't understand it's nature and usefulness. You explained very well, great examples. P.S. If you followed author as me while writing code you can notice that final output isn't same as in video. Just add this method in Video Clip record: public override string ToString() => $"{Video.Title} [{Interval.Offset} - {Interval.Offset + Interval.Duration}]";
The reality of high-performance implementation is some degree mandatory mutation. Luckily, C# is in a much better place about this still as the video could hold a ReadOnlySequence or a SequenceReader or unsafe reusable buffer given videos tend to be huge and byte[] can only be only 2GiB big, or even custom construct to represent offsets into a file and a file handle - would be a bad idea to keep such large data in memory until you need to process it, so new clip operations would preserve original source and state just re-slicing it, if applicable. In any case, once we are in this kind of domain, C# is ought to be approached as a terse successor to C++ that shares numerous features with Rust (which I strongly recommend to any C# programmer to get acquainted with - it transformed the way I construct and manage the state). In any case, it is unfortunate our ecosystem is viewed as "less popular Java" which completely disregards the reality of the wide domain and multi-paradigm nature of C#.
Great video, I loved the way you first showed the problem and then solution to the problem. The only thing that I did not find very readable is the chained ternary operators at Clipping. Thank you!
@@tilentratnjek8743 Yes, that part with ternary operators is forced. I hope to see dynamic patterns in some future versions of C#. That would establish the position of switch expressions as the C# variant of match expressions from proper functional languages.
@@zoran-horvat is there any reason against using switch expressions like this for this scenario? public static TimeInterval Clip(this TimeInterval interval, TimeInterval clip) => (interval, clip) switch { { } when interval.Duration TimeInterval.Create(interval.Offset + interval.Duration, TimeSpan.Zero), { } when interval.Duration TimeInterval.Create(interval.Offset + clip.Offset, interval.Duration), { } => TimeInterval.Create(interval.Offset + clip.Offset, clip.Duration) };
@@thomasg.6113 I just don't like it with an empty pattern part. Personal preference... P.S. My objection in the video was about lacking support for dynamic condition so that we don't have to use the when clause. That would save a lot of horizontal space which is already scarce in pattern matching expressions.
Until we get dynamic patterns I'd rather just see simple if statements with returns instead of nested ternaries. They may be twice as many lines but I find them ten times as readable.
After some testing and playing with this code I met a bug. If you want to chunk video into clips by 1 minute you see strange result. Video duration 5 min Clip duration 1 min Step 1 min Output: C# Records Are Funny [00:00:00 - 00:01:00] C# Records Are Funny [00:01:00 - 00:02:00] C# Records Are Funny [00:02:00 - 00:03:00] C# Records Are Funny [00:03:00 - 00:04:00] C# Records Are Funny [00:04:00 - 00:09:00] Last output is incorrect. I fixed it like (edit file VideoClip.cs line 25) : interval.Duration < clip.Offset + clip.Duration ? TimeInterval.Create(interval.Offset + clip.Offset, interval.Duration - (interval.Offset + clip.Offset))
Great Video, I use Record's all the time, it really speeds up development. Pattern matching is fun, and efficient. Side Note Functional Programming is a "HELL" of a Drug!
@@ABMedia83 Many old-school programmers frown upon functional elements being added to C#, but I've never ever seen a man who learns functional programming to any depth and then leaving it. It's only possible for one to choose not to start learning it, which is a problem in itself.
@@zoran-horvat Yeah, I get it, it took me years to get used to the Lamba Expression and Extension's Method's, but once you learn them, you don't want to go back. I started with C# 2.0 (Back when it was still considered a copy of Java). Your channel has help me a lot.
Looks cool, but the code is way too complicated for something so simple. I will never write something like this, I hope I will not have to change code written like this. Looks nice though
@@7th_CAV_Trooper They made a forum post update a couple weeks ago saying they finished updating the editor and runtime player to .Net 8 with CoreCLR. They just need to finish a couple more things, but by the sounds of it they will be showing some stuff at Unite possibly. Don't know what they plan to mention at Unite, but hopefully it is something good for it. Unite is in September so coming up within a couple weeks.
There is no magic in records, they are a shorthand for creating getter only properties along with overriding the default object methods to make it simpler to compare, print and so on. The idea of unity is that you will have objects that are called every frame, so they threw out the OOP out the window for performance. OOP and functional principles however play a large role for making readable business applications, while games are simpler on business logic and not as relational.
@@rennasccenth It would be better if I added a few helper methods to give names to boolean expressions and mappings. But I agree that they are not the best thing to do. I hope switch expressions will support dynamic patterns soon and then I will retire the chained ternaries.
Thanks for the great video! When we split class functionality between the class A and a static class B with extension methods for class A, are there valid reasons to call the extension methods from the class A itself? E.g. to keep the classes small. Or is this creating unnecessary coupling?
@@g4schd I don't see the justification for such a design, except for making the class smaller. Extension methods are usually defined when someone else wishes to add a feature on top of the components/values exposed by the class. The methods defined on the class itself would not depend on the external feature because it was likely already there when that feature was added.
Thanks for the content. I personally don't like records when you need validation, the static factory method helps but I prefer coding by design, not by rules. I have a question, why do you have the Populate as static methods in extension classes and not put them in the Video and VideoMedia class? Especially in functional programming and immutable classes, it is common to have them as instance methods instead. Thanks!
@@danflemming3553 By separating functions into an extension class, you have the freedom to keep them in a more specialized namespace, even the one that is external to the assembly where the record is defined. We organize functional code quite differently than object-oriented code. Defining operations as instance-level methods on the record deprives us from that flexibility.
@@zoran-horvat Let's take an example. What is the advantage of have the VideoMedia.Populate(..) method as extension method, compared to have it as an instance in the record and have it return a new VideoMedia instance with the content loaded? You could call it LoadContent(Funct load). Or maybe the content should be abstracted into a VideoMediaContent
@@danflemming3553 The point is that it is not about a method. It is about dozens of methods. Another question also raised in the video is about the same operations applying to different types. Both issues are addressed elegantly in FP by grouping the functions by their role, rather than by (and in) the type.
In a previous video you put the Create() static functions in a separate static class. In this one you incorporated them directly into the record. Have you changed your mind about the best place to put them? And if so, is it just for aesthetics, or is there some particular justification you're focusing on?
@@David-id6jw No, I still add them to a separate class. The reason why I did it this way now is that many programmers get frightened of moving the methods out and close the video before it does something to them.
@@danilomosurovic6735 10/16 modules done. I am progressing on it now, after a long pause while I was establishing this channel. Might be out around the end of the year.
@@pd5711 It is actually easier than testing classes! These static methods produce no side effects. Their output is always the function of their inputs. Furthermore, they return instances of records, which implement value-typed equality. Therefore, all you need to do in testing is check whether the result is equal to what you expected, given the inputs. All this makes testing very easy in functional code, compared to object-oriented code in general.
@@zoran-horvat Psst! Your static methods have side effects. At 07:00 you are doing IO (File.ReadAll...)! Static classes can have static contructors and mutate and be just like normal classes, with the exception that there can be only 1 instance. Additionaly there are a lot of static methods in .NET that depend on CultureInfo, and are both unpure and side effect-y, so even "pure" code will be inf3cted from the start.
Good video that is a great example of using records! But personally I would prefer disign where Clip method creates new video, not mutate existed one, isn't it solves the whole issue with excessive ResetClipping? I mean disign should be based on your needs and if you need to make a clip out of video - Clip() should make one and if you need to edit the existing one - you should implement Edit() method of some sort, right? Or if we speak about some editing tool then maybe Clip()/Crop() method would mutate the video and CreateClip()/CopySelection() would create new one without mutating excited video. There is many possible solutions to achieve programmer needs without complete redesign of existing codebase, which will cost a lot if you work for a business and not making pet project for yourself)
Wow Zoran, that's dense. Got lost in the Populate extension method, why do a switch? Surely a programmer knows when he has already populated an object or not? What am I not understanding? About the switch, I've had it hammered home to eliminate them. How about an Interface with a couple of derived sub-classes?
@@nickbarton3191 Your proposal is a valid OO design. In this video, however, I tried to give , you a glimpse of a typical way of organizing and implementing behavior in functional programming. Switch expressions are a principal way of implementing behavior in functional code because functional models are based on records consisting of records. That means that all objects and all their components implement value-typed semantics, and hence are useful as patterns in switch expressions with no additional code. I strongly encourage every C# programmer to learn F#, at least to a basic level. In F#, the match expressions are the principal way of defining behavior.
@@zoran-horvat Yes, I can see that it would eliminate a lot of boilerplate code. In this case, it's simple, but in the large, without care, you could end up maintaining lots of large switch cases. I've done some exercises in F#, I realise this channel is dedicated to C#, but a video comparing and contrasting the two could be a very interesting topic.
In the contrived example for OOP contrasted versus the contrived example for functional types shown in the video you mention that you have to exercise some discipline when using records. The same argument could be made in the OOP example where certain design decisions, and your own admittedly poor state management, were made that could've avoided the contrived bug. An interesting demonstration of records, but an unconvincing argument for functional over OOP. For all I know you could be absolutely correct to argue for the record approach here but your deliberately poor OOP sample code doesn't do a great job of making that clear. In the future if you want to make a case for your preferred approach, steel man it by actually trying to create the best representation of the approach you're arguing against. You failed to do that here.
@@jakotheshadows87 You are right, except that FP is leaving you much less room for design mistakes than OOP does. Even entire classes of bugs that are common on OOP are impossible to make in FP. It's easy to speak against functional practices when you have not adopted them. Have you ever met a programmer who has adopted functional practices and then not applied them to OOP? Such a programmer is yet to be born.
How about a proper video going over new concepts like records that have been implemented relatively recently (last 5 to 10 years) that us older, returning C# or Unity (Unity is still stuck in C# 9.0ish) programmers might not have even heard about, and which are completely outside of our radas to even consider to implement as part of our toolkit A video where you take for example the 10 most important "new" concepts like records that you think people are either under or misusing, and go over what they are, why they were added, what role they are supposed to play and ofc. the common mistakes, known good practices or underused/overlooked applications for them like you would to a complete beginner I'd watch that instantly! 😊
Zoran recorded one like that already - ruclips.net/video/RfEbn9aXY-Y/видео.htmlsi=CqVWjkU_I7KnNVnq Of course, he just stating what features of C# was added that support and embrace functional design without deep examples or such. But I think, most of developers can learn almost all of them by reading the docs or can find videos about more intricate ones on Zoran's channel (like pattern matching with new switch syntax or records)!
@@djoufson-dev It is not an issue in general. Think about it: all functional languages would have it; LISP would have it 50-60 years ago, when we had 10.000 times less memory compared to now.
Sure, and look at what OOP does to caches and branch prediction. We're far beyond counting frames and clock cycles unless given an extreme circumstance.
@@adambickford8720 About a decade ago I read a paper where researchers claimed they have measured that 30% of execution time of C++ programs was spent resolving virtual functions. Compilers have advanced since then but I doubt that they have improved on this a lot. Hence, we can view late dispatch of calls as the greatest power of OOP, but also its greatest liability. That is the signature of any tradeoff. An interesting side note is that Rust has a compile-time optimization to figure when a lambda is effectively non-virtual and to turn it into a statically resolved call. Another interesting side note is to ask whether we should really bother with these low-level implementation details when doing design? You will note in my videos that I actively remove those topics from the table.
I'm all for showing the benefits of one style of programming over another, but your OO code is contrived and designed to fail. Using a video class that mutates itself inside a loop was obviously flawed. If you are going to champion Functional over OO, you still need to make an honest effort on the OO example, otherwise this is just disingenuous and lacks credibility. Go back and fix your OO example, then do the final comparison.
@@kimblemojimble7967 Actually, there is an underlying story behind that OO example, which is objectively done wrong. A good part of my job was reading other people's code. I have seen bloated classes like that more often than not. The reason is that programmers grow a habit of adding more to an existing class, rather than separating concepts early on. So, even though I agree that the class in the demo is designed the wrong way, I don't agree that I haven't displayed OOP in its true light. I have at least attempted to display the realistic result of applying the OO coding style in practice. BTW that's why I have mentioned that there would have to be a redesign/refactoring soon. That is another typical trait of OOP in teams, as they make a bloated class and only then refactor it into smaller ones.
Perfect illustration of en.m.wikipedia.org/wiki/Architecture_astronaut I’m neither pro OOP nor pro FD- each is a tool for the job. And understanding where each fits best is valuable. Not blaming one or the other.
"architecture astronaut is a term for an individual who is focused on abstract ideas underpinning software design." Oh, so a professional software engineer?
@@7th_CAV_Trooper I don't think the commenter gave much thinking to his own post. But I give him credit for knowing about this Wikipedia page and being ready to slam it on a random RUclips video comments page.
@@zoran-horvat sometimes I get flak at work for thinking before coding. These same people come to me later to ask why all of my projects are well known success stories. 😉
What? The video is literally illustrating practical reasons why OOP is not a good solution for this particular design problem. The videos I've seen on this channel have been far more grounded than most content on design, and many include strong examples and applications of OOP, so your insinuation of a bias falls flat. It is perfectly fair and sensible to blame the improper use of OOP for a downside that is caused by application of that methodology.
Once again thank you Zoran. This discussion of records, domain modelling and FD has stimulated the brain over breakfast and has motivated me to reconsider my approach to solving tricky situations in a more elegant way.
Zoran i hope that one day i can use C# like you
There is so much to love in this language
Thank you for sharing your knowledge
This is not coding :D - this is art
First off: excellent video as always Zoran.
Secondly: you can achieve even more explicit controls by using immutable record structs with private property setters to enforce creation through factory methods. This also enforces that discipline to use factory methods since you can't create them without it.
If you need the with {...} functionality then just bump the properties to init-only.
I've started taking this approach much more frequently as it also fixes the nullability issues with regular records, since they are really just classes with a fake moustache. You run into issues where the underlying properties may be objects that are returned by reference and are therefore mutable. If it's structs all the way down then they're explicitly by-val and can't mutate without reflection.
@@alexlawrence1337 I see what you mean. However, I tried to show vanilla records, in their natural state of one-liners, so that you can judge how far you should go.
@@zoran-horvat and there is definitely elegance in the brevity and simplicity of that approach. My only apprehension is I know that some developers love to abuse the default keyword, which skips constructors entirely and would initialise a record as null. This happens a lot when records are fields of API requests and aren't appropriated tagged as required, or when some developers see the warning that a field isn't initialized on construction and so they panic and initialise it as default instead of the empty factory method.
It's more so an issue when the record you're creating isn't a dto or poco, but rather when you're implementing some other functional structure such as a monad. I do not like my monads to be null ever! Nor do I like dealing with nullable reference types when I don't have to, as nullable typed become almost poisonous, requiring null guards everywhere they are passed.
It does also take a keen mind to write those structs in such a way that default becomes a valid state, as any structs within your struct are created with its properties set to 0.
As much as I love the overall advice on designing with records, if I saw a pull request with that mess of ternaries traveling off the screen in VideoClip.cs:24-26, I would send it back to the dev. Breaking that down into much more readable conditional blocks makes the code far more maintainable and understandable at a glance, and ultimately still compiles to the same IL. "Compact" or "concise" does not always equal "good" in coding. You did make it overall easier to read, but not that part.
@@lightandtheheat There is truth in that. I wanted to show the alternative to switch expressions where they do not support a particular dynamic pattern. In the end, this demo may serve as a motivation to extend switch expression syntax.
that ternary operator can be read just like a switch expression, is just a matter of what you are used to.
@@ghevisartor6005 It would look way less intimidating if I introduced helper methods to give names to patterns and corresponding expressions, such as WhenNotOverlapping, WhenInside, etc.
I havent watched the video but i did watch your functional c# course on pluralsight. Tried the records and tricks from there. Now i use them a loooot more. Was an awesome course btw.
Coming from F# land, this is so promising. Far from ideal, but promising! Waiting paitently for C# to adopt the rest of the magic of F#'s workflow!
Btw, great video. You definitly got my sub!
Your code is always so elegant. I strive to make something as neat as this someday.
Yes, I don't call public constructor, but others will surely do..
@@ShowoffFantasy Why? Are others stupid?
@@zoran-horvat they're probably not but readonly keyword didn't appear from nothing.
@@ShowoffFantasy What has readonly have to do with the public constructor? Properties are already readonly anyway. I don't see your point.
While it obviously would be even nicer if one could disallow that it's not something that's that important imho. If you can't even trust your coworkers with this then you have bigger issues in your team.
In the end it's about trade-offs and you gain so much and only lose a tiny bit.
@@Rick104547 That was my point, too. C# makes it super-easy this way. It comes with a drawback that we must be disciplined when coding. But we must be disciplined on a hundred other things in functional programming, so why fear it? There are easier ways to cause trouble than calling a generated constructor.
Records are a abstraction (less distraction) of immutable implementation in classes. While they speed up programming they have overlapping behavior that is not always utilized, you mentioned the public constructor.
Personally i find nesting types a great way of encapsulating behavior, reducing the surface area of bugs while also helping other developers contextualize the types in meaningful way. I also find it more comprehensible if the 'main' type owns the behavior instead of exrensionmethods.
Great video, please make more 🧑🔧
Thank you for this video. I avoided using of C# records because don't understand it's nature and usefulness. You explained very well, great examples.
P.S. If you followed author as me while writing code you can notice that final output isn't same as in video. Just add this method in Video Clip record:
public override string ToString() => $"{Video.Title} [{Interval.Offset} - {Interval.Offset + Interval.Duration}]";
The reality of high-performance implementation is some degree mandatory mutation. Luckily, C# is in a much better place about this still as the video could hold a ReadOnlySequence or a SequenceReader or unsafe reusable buffer given videos tend to be huge and byte[] can only be only 2GiB big, or even custom construct to represent offsets into a file and a file handle - would be a bad idea to keep such large data in memory until you need to process it, so new clip operations would preserve original source and state just re-slicing it, if applicable. In any case, once we are in this kind of domain, C# is ought to be approached as a terse successor to C++ that shares numerous features with Rust (which I strongly recommend to any C# programmer to get acquainted with - it transformed the way I construct and manage the state). In any case, it is unfortunate our ecosystem is viewed as "less popular Java" which completely disregards the reality of the wide domain and multi-paradigm nature of C#.
Great video, I loved the way you first showed the problem and then solution to the problem. The only thing that I did not find very readable is the chained ternary operators at Clipping. Thank you!
@@tilentratnjek8743 Yes, that part with ternary operators is forced. I hope to see dynamic patterns in some future versions of C#. That would establish the position of switch expressions as the C# variant of match expressions from proper functional languages.
@@zoran-horvat is there any reason against using switch expressions like this for this scenario?
public static TimeInterval Clip(this TimeInterval interval, TimeInterval clip) => (interval, clip) switch
{
{ } when interval.Duration TimeInterval.Create(interval.Offset + interval.Duration, TimeSpan.Zero),
{ } when interval.Duration TimeInterval.Create(interval.Offset + clip.Offset, interval.Duration),
{ } => TimeInterval.Create(interval.Offset + clip.Offset, clip.Duration)
};
@@thomasg.6113 I just don't like it with an empty pattern part. Personal preference...
P.S. My objection in the video was about lacking support for dynamic condition so that we don't have to use the when clause. That would save a lot of horizontal space which is already scarce in pattern matching expressions.
Until we get dynamic patterns I'd rather just see simple if statements with returns instead of nested ternaries. They may be twice as many lines but I find them ten times as readable.
i do this:
condition
? condition
? then
: else
: else
Makes it much easier to follow imho, like a decision tree
Great video! Really great content! :)
Amazing!
After some testing and playing with this code I met a bug. If you want to chunk video into clips by 1 minute you see strange result.
Video duration 5 min
Clip duration 1 min
Step 1 min
Output:
C# Records Are Funny [00:00:00 - 00:01:00]
C# Records Are Funny [00:01:00 - 00:02:00]
C# Records Are Funny [00:02:00 - 00:03:00]
C# Records Are Funny [00:03:00 - 00:04:00]
C# Records Are Funny [00:04:00 - 00:09:00]
Last output is incorrect. I fixed it like (edit file VideoClip.cs line 25)
: interval.Duration < clip.Offset + clip.Duration ? TimeInterval.Create(interval.Offset + clip.Offset, interval.Duration - (interval.Offset + clip.Offset))
@@eugene-dmitrievich Nice one! Thank you.
Great Video, I use Record's all the time, it really speeds up development. Pattern matching is fun, and efficient.
Side Note
Functional Programming is a "HELL" of a Drug!
@@ABMedia83 Many old-school programmers frown upon functional elements being added to C#, but I've never ever seen a man who learns functional programming to any depth and then leaving it. It's only possible for one to choose not to start learning it, which is a problem in itself.
@@zoran-horvat Yeah, I get it, it took me years to get used to the Lamba Expression and Extension's Method's, but once you learn them, you don't want to go back. I started with C# 2.0 (Back when it was still considered a copy of Java). Your channel has help me a lot.
Looks cool, but the code is way too complicated for something so simple. I will never write something like this, I hope I will not have to change code written like this. Looks nice though
I really like the idea of records, however Unity doesn't (properly) support them and they don't really serialise :(
Unity is very far behind the C# standard. I thought ditching mono for dotnet was on the roadmap.
@@7th_CAV_Trooper They made a forum post update a couple weeks ago saying they finished updating the editor and runtime player to .Net 8 with CoreCLR. They just need to finish a couple more things, but by the sounds of it they will be showing some stuff at Unite possibly. Don't know what they plan to mention at Unite, but hopefully it is something good for it. Unite is in September so coming up within a couple weeks.
There is no magic in records, they are a shorthand for creating getter only properties along with overriding the default object methods to make it simpler to compare, print and so on. The idea of unity is that you will have objects that are called every frame, so they threw out the OOP out the window for performance. OOP and functional principles however play a large role for making readable business applications, while games are simpler on business logic and not as relational.
I really like almost all things here, but the chained ternary always blew my mind. They are pretty much discomfortable to read
@@rennasccenth It would be better if I added a few helper methods to give names to boolean expressions and mappings. But I agree that they are not the best thing to do. I hope switch expressions will support dynamic patterns soon and then I will retire the chained ternaries.
Thanks for the great video! When we split class functionality between the class A and a static class B with extension methods for class A, are there valid reasons to call the extension methods from the class A itself? E.g. to keep the classes small. Or is this creating unnecessary coupling?
@@g4schd I don't see the justification for such a design, except for making the class smaller. Extension methods are usually defined when someone else wishes to add a feature on top of the components/values exposed by the class. The methods defined on the class itself would not depend on the external feature because it was likely already there when that feature was added.
Thanks for the content. I personally don't like records when you need validation, the static factory method helps but I prefer coding by design, not by rules.
I have a question, why do you have the Populate as static methods in extension classes and not put them in the Video and VideoMedia class? Especially in functional programming and immutable classes, it is common to have them as instance methods instead. Thanks!
@@danflemming3553 By separating functions into an extension class, you have the freedom to keep them in a more specialized namespace, even the one that is external to the assembly where the record is defined. We organize functional code quite differently than object-oriented code. Defining operations as instance-level methods on the record deprives us from that flexibility.
@@zoran-horvat Let's take an example. What is the advantage of have the VideoMedia.Populate(..) method as extension method, compared to have it as an instance in the record and have it return a new VideoMedia instance with the content loaded? You could call it LoadContent(Funct load). Or maybe the content should be abstracted into a
VideoMediaContent
@@danflemming3553 The point is that it is not about a method. It is about dozens of methods.
Another question also raised in the video is about the same operations applying to different types.
Both issues are addressed elegantly in FP by grouping the functions by their role, rather than by (and in) the type.
In a previous video you put the Create() static functions in a separate static class. In this one you incorporated them directly into the record. Have you changed your mind about the best place to put them? And if so, is it just for aesthetics, or is there some particular justification you're focusing on?
@@David-id6jw No, I still add them to a separate class. The reason why I did it this way now is that many programmers get frightened of moving the methods out and close the video before it does something to them.
Zoran, how is the progress on your OOP course on Udemy, and do you have an estimated release date? Tnx in advance.
@@danilomosurovic6735 10/16 modules done. I am progressing on it now, after a long pause while I was establishing this channel. Might be out around the end of the year.
Thanks Zoran, that's really intrering.
However - Im just wondering - how we should test that type of code with all of the static methods used there ?
@@pd5711 It is actually easier than testing classes! These static methods produce no side effects. Their output is always the function of their inputs. Furthermore, they return instances of records, which implement value-typed equality.
Therefore, all you need to do in testing is check whether the result is equal to what you expected, given the inputs.
All this makes testing very easy in functional code, compared to object-oriented code in general.
@@zoran-horvat Psst! Your static methods have side effects. At 07:00 you are doing IO (File.ReadAll...)! Static classes can have static contructors and mutate and be just like normal classes, with the exception that there can be only 1 instance. Additionaly there are a lot of static methods in .NET that depend on CultureInfo, and are both unpure and side effect-y, so even "pure" code will be inf3cted from the start.
so when you avoid checking of states by copying objects, that means less computations but more allocations.
Good video that is a great example of using records!
But personally I would prefer disign where Clip method creates new video, not mutate existed one, isn't it solves the whole issue with excessive ResetClipping?
I mean disign should be based on your needs and if you need to make a clip out of video - Clip() should make one and if you need to edit the existing one - you should implement Edit() method of some sort, right?
Or if we speak about some editing tool then maybe Clip()/Crop() method would mutate the video and CreateClip()/CopySelection() would create new one without mutating excited video.
There is many possible solutions to achieve programmer needs without complete redesign of existing codebase, which will cost a lot if you work for a business and not making pet project for yourself)
Wow Zoran, that's dense.
Got lost in the Populate extension method, why do a switch? Surely a programmer knows when he has already populated an object or not? What am I not understanding?
About the switch, I've had it hammered home to eliminate them. How about an Interface with a couple of derived sub-classes?
@@nickbarton3191 Your proposal is a valid OO design. In this video, however, I tried to give , you a glimpse of a typical way of organizing and implementing behavior in functional programming.
Switch expressions are a principal way of implementing behavior in functional code because functional models are based on records consisting of records. That means that all objects and all their components implement value-typed semantics, and hence are useful as patterns in switch expressions with no additional code.
I strongly encourage every C# programmer to learn F#, at least to a basic level. In F#, the match expressions are the principal way of defining behavior.
@@zoran-horvat Yes, I can see that it would eliminate a lot of boilerplate code. In this case, it's simple, but in the large, without care, you could end up maintaining lots of large switch cases.
I've done some exercises in F#, I realise this channel is dedicated to C#, but a video comparing and contrasting the two could be a very interesting topic.
So much common sense ... in retrospect ;)
Brilliant
In the contrived example for OOP contrasted versus the contrived example for functional types shown in the video you mention that you have to exercise some discipline when using records. The same argument could be made in the OOP example where certain design decisions, and your own admittedly poor state management, were made that could've avoided the contrived bug. An interesting demonstration of records, but an unconvincing argument for functional over OOP. For all I know you could be absolutely correct to argue for the record approach here but your deliberately poor OOP sample code doesn't do a great job of making that clear. In the future if you want to make a case for your preferred approach, steel man it by actually trying to create the best representation of the approach you're arguing against. You failed to do that here.
@@jakotheshadows87 You are right, except that FP is leaving you much less room for design mistakes than OOP does. Even entire classes of bugs that are common on OOP are impossible to make in FP.
It's easy to speak against functional practices when you have not adopted them. Have you ever met a programmer who has adopted functional practices and then not applied them to OOP? Such a programmer is yet to be born.
How about a proper video going over new concepts like records that have been implemented relatively recently (last 5 to 10 years) that us older, returning C# or Unity (Unity is still stuck in C# 9.0ish) programmers might not have even heard about, and which are completely outside of our radas to even consider to implement as part of our toolkit
A video where you take for example the 10 most important "new" concepts like records that you think people are either under or misusing, and go over what they are, why they were added, what role they are supposed to play and ofc. the common mistakes, known good practices or underused/overlooked applications for them like you would to a complete beginner
I'd watch that instantly! 😊
Zoran recorded one like that already - ruclips.net/video/RfEbn9aXY-Y/видео.htmlsi=CqVWjkU_I7KnNVnq
Of course, he just stating what features of C# was added that support and embrace functional design without deep examples or such. But I think, most of developers can learn almost all of them by reading the docs or can find videos about more intricate ones on Zoran's channel (like pattern matching with new switch syntax or records)!
So c# records are just rust structs?
@@__abd__ Similar, yes. There are differences, mainly in how heap and stack are managed in the two languages.
@@zoran-horvat ye didn't mean memory wise just how they "function"
@@__abd__ Yes, semantically they are the same thing.
Awesome, but let's point out memory consumption here 🤔
@@djoufson-dev It is not an issue in general. Think about it: all functional languages would have it; LISP would have it 50-60 years ago, when we had 10.000 times less memory compared to now.
Sure, and look at what OOP does to caches and branch prediction.
We're far beyond counting frames and clock cycles unless given an extreme circumstance.
@@adambickford8720 About a decade ago I read a paper where researchers claimed they have measured that 30% of execution time of C++ programs was spent resolving virtual functions. Compilers have advanced since then but I doubt that they have improved on this a lot. Hence, we can view late dispatch of calls as the greatest power of OOP, but also its greatest liability. That is the signature of any tradeoff.
An interesting side note is that Rust has a compile-time optimization to figure when a lambda is effectively non-virtual and to turn it into a statically resolved call.
Another interesting side note is to ask whether we should really bother with these low-level implementation details when doing design? You will note in my videos that I actively remove those topics from the table.
I'm all for showing the benefits of one style of programming over another, but your OO code is contrived and designed to fail. Using a video class that mutates itself inside a loop was obviously flawed. If you are going to champion Functional over OO, you still need to make an honest effort on the OO example, otherwise this is just disingenuous and lacks credibility. Go back and fix your OO example, then do the final comparison.
@@kimblemojimble7967 Actually, there is an underlying story behind that OO example, which is objectively done wrong. A good part of my job was reading other people's code. I have seen bloated classes like that more often than not. The reason is that programmers grow a habit of adding more to an existing class, rather than separating concepts early on.
So, even though I agree that the class in the demo is designed the wrong way, I don't agree that I haven't displayed OOP in its true light. I have at least attempted to display the realistic result of applying the OO coding style in practice.
BTW that's why I have mentioned that there would have to be a redesign/refactoring soon. That is another typical trait of OOP in teams, as they make a bloated class and only then refactor it into smaller ones.
See nothing inherently functional in records. But of course they can be used this way. As well as regular classes
There is nothing inherently OO in classes, either. Any functional design is built of classes anyway.
It's almost like a new language at this point
Thank you Zoran please read Quran
Perfect illustration of en.m.wikipedia.org/wiki/Architecture_astronaut I’m neither pro OOP nor pro FD- each is a tool for the job. And understanding where each fits best is valuable. Not blaming one or the other.
If that was supposed to be me, I don't recognize myself. Wasn't I showing a particular implementation rather than high level design?
"architecture astronaut is a term for an individual who is focused on abstract ideas underpinning software design."
Oh, so a professional software engineer?
@@7th_CAV_Trooper I don't think the commenter gave much thinking to his own post. But I give him credit for knowing about this Wikipedia page and being ready to slam it on a random RUclips video comments page.
@@zoran-horvat sometimes I get flak at work for thinking before coding. These same people come to me later to ask why all of my projects are well known success stories. 😉
What? The video is literally illustrating practical reasons why OOP is not a good solution for this particular design problem.
The videos I've seen on this channel have been far more grounded than most content on design, and many include strong examples and applications of OOP, so your insinuation of a bias falls flat.
It is perfectly fair and sensible to blame the improper use of OOP for a downside that is caused by application of that methodology.