I struggled with this issue with oop for many years but as i have learned a bit of accounting, it helped me crack the issue. If you think of invoice as an object, you feel that it has behaviour. But invoice is simply a record as you call it in functional programming. What does it record? It records the services provided or items sold or a combination of both (example your car mechanic invoice which includes labour charges and parts sold). The payment for an invoice is a separate record and needs to be tracked separately. You may be required at some point in the life of the app how the payment was done (cash, card, wire transfer etc). There may be partial payment or there may be payment of more than one invoice through single instrument like a bank cheque. The state of the invoice is two additional attributes, if it is paid and if yes on which date, come from a different data source and should not ideally map to same table. A transactional database and a reporting database are two different designs. So if you are checking only one of them. If you are checking paid status of the invoices, it's coming from reporting db and that's a read only data. The payment operation you do on the invoice goes to transactional db and it would at times take a while to reflect in reporting db. So this is not as simple problem to club all these concerns into one sample. Oop at times requires deeper thinking of the domain to solve the problem correctly.
The application from the video is not the actual accounting application. As you have mentioned, in an actual business, we would store proofs of payments, rather than statuses. In that light, an invoice could also be partly paid, have an associated late payment interests, past dues, fees, etc. All those details would be too much for a TH-cam video showing a technical issue in code.
With the OOP solution, an append only dB schema solves the problem. Then you get a queriable ledger, quite often a requirement in large systems. It also solves the locking problem as we've discussed before. But for a small application, it hardly matters. Yes, I understood that the demo was about introducing functional concepts, I'm still thinking about the ramifications of that.
A function in a functional programming language is equivalent to a single method interface in object oriented programming. An object in object oriented programming is equivalent to a closure in functional programming. You don't really miss anything using one or the other paradigm, but ease of use will definitely be an important factor in choosing the paradigm to use. Applying SOLID principles in OOP makes your code more similar to functional code, that is small objects and small interfaces with small state and small number methods and with little or no subclasses.
I agree with everything you said, with a note that must be added for completeness. There is one large distinction, and that is the fundamental distinction between OOP and FP: objects tend to expose void methods. That design decision is backed by ORM-s and, just to name two. The older I am, the more I dislike mutable design. This video is one in the series that underlines my preference toward immutable design, and in that case OOP and FP become equivalent in the way you have explained.
I agree with everything you said, with a note that must be added for completeness. There is one large distinction, and that is the fundamental distinction between OOP and FP: objects tend to expose void methods. That design decision is backed by ORM-s and, just to name two. The older I am, the more I dislike mutable design. This video is one in the series that underlines my preference toward immutable design, and in that case OOP and FP become equivalent in the way you have explained.
Joe Rainsberger in one of his talks said something like: "...with well designed OOP an object is just a set of partially applied functions". I like that.
@@ddanielsandberg I am not aware of that citation, but if I may guess it is based on observation that an object may combine its private state with the arguments and in that way act identically to a partially applied function in any functional language. In that sense, I agree fully with that quote.
There is the difference. In OOP, an ORM is tracking updates. When State pattern is used, that mechanism is broken and every caller of the State object must know that. In FP, ORM does not track updates, but it is rather used in a different operational mode. Therefore, these few classes would not be treated separately from others and their callers would have no new responsibility to implement. The whole code remains uniform with or without this feature.
@@zoran-horvat thx Zoran. frankly I fo not see where it is simpler still. You have to implement the update method both case. Dont you? Dont get me wrong, I am asking because I am learning.
@@LE8271 It would be very difficult to explain the upsides of FP in a comment. There are other comments here where I tried it in more words. I would certainly point you to learn about functional modeling and you will advance, no doubt. The short version is that, in modern applications, one team defines the model and another team adds behavior on top of it. That is precisely how FP puts it, and exactly opposite to how OOP puts it. That is why C# is attaining more and more of the FP syntax in recent years. That method simply pays off in application development.
You can change the paid function call type to just accept OutstandingInvoice it will put responsibility on the client to pattern match the invoice and call paid function
Can you please simply explain why a nullable PaidOn property in a single Invoice type would be bad? I think it's a perfect model of a real-world invoice. Real-world invoice can be stamped with a red colored paid on this date and the null state of PaidOn property would mean the real-world invoice doesn't have the red stamp on it yet. What's wrong with this simple model? Why complicate it with multiple types like UnpaidInvoice and PaidInvoice?
As a matter of fact, I can simply explain the matter. Adding a nullable state, as in the best tradition of procedural design of the 1980s, would require all operations (dozens of them on an important model!) to contain a branching instruction and to implement two operations each, rather than just one. Quite often, consuming code would also use branching, rendering any subsequent evolution of the underlying model painful, if not impossible. That decision would add to cyclomatic complexity of dozens of methods, double the number of unit tests for them, but also help plant a few bugs here and there, bugs that would otherwise be impossible. The short answer is: That would be a flat design flaw. You could make it work, but it would be a flaw nonetheless. For the end, sincere advice: Learn programming beyond nullable state; learn languages that don't even have a null, at least not the one you can use in custom code. You will see a different programming paradigm that works much better than the programming of old.
@@zoran-horvat Thank you. It's a nice explanation from your point of view but personally I don't see a big problem with implementing branching code. For many decades, millions of applications and games were written with OOP and branching code and they served millions of people and made billions of dollars so I think the idea of banishing the branching code is not necessary even if it sounds and looks beautiful. I think the complexity created by having multiple different types for a single real-world object like an Invoice is much worse than having some branching code here and there. You are one of the rare people I highly respect on TH-cam based on their programming (especially in C#) experience and I like your ideas and teachings but I guess we won't agree on this specific topic. Thank you very much!
@@XtroTheArctic I am consulting two companies right now which were thinking that same way. Millions in, and millions more. Until the cost of going forward started to burn twice that much, for the reasons I outlined above. Doing things that way can make the code work, but only until it grinds to a halt. Programmers usually solve the problem by quitting and finding a new job, but that doesn't make the company's software work beyond the point where expenses overgrow revenues.
But it also means that we don't need several classes to design the solution. We can have one Invoice class with a propery "PaymentStatus" - can be enum as well. Yes I know it is redudant with a PaidDate? but it also can give a clean info about the invoice. Also I would not rely on a system date about payment. Also we can introduce a new class Payment wich can have Invoice reference. So that class can be used for payments. Samo kažem. Also let not mix ORM entities with domain objects.
@@zoran-horvatVladimir Horikov has an interesting opinion about class pollution where you can have a simpler solution instead of a hierarchical set of classes, which increases in complexity with each new property that will be added.
How would you load an invoice from the database so it's the correct type? I assume it's all in one table "Invoices" of entity type Invoice, so how would you load a PaidInvoice or OutstandingInvoice?
There must be a conversion from raw database entities to proper model entities, given a time parameter. That is one of the hidden gems in this video, which is also a story of working with time. Since the status of an invoice depends on when the query is executed, it certainly cannot be stored in the database. I would only store the facts, such as events that happened at certain time.
Interesting video but I might have missed something. The idea behind the State pattern is to decouple the class from its state. Therefore shouldn't you have Invoice and an associated InvoiceState? So what you specialize is the state, not the invoice: PaidState, OutstandingState, ... The behavior of the invoice will then change as its state changes, as if the invoice changed its class.
I don't think that is the purpose of that pattern. That _could be_ the purpose in some implementations if coupling to state causes design issues, but it would be an overstatement to say that should always be the case. I cannot remember my goals at the time of making this video, but I believe, from the outcome, that the state did not bother me too much, compared to state transitions.
Interesting! Although, I'm fairly sure that in 99% of cases, State patterns with OOP will not be used, but rather a pragmatic solution will be used. So in this case, the PaidOn? property will just be added to the base class to avoid the hassle. But it's an interesting approach nevertheless.
First, what you call pragmatic approach I call turning an object-oriented solution into a procedural one, with all the negative consequences - primarily moving operations to the consumer. Second, while you can evade the problem in a simple case, actual cases in practice are much more complex and such a workaround would not even be available.
@@zoran-horvat Yeah, I am with you on that one. I'm also leaning towards your immutable, functional approach when necessary. The OOP state pattern feels very clunky. But I was just stating what will probably happen in practice most of the time 😄
@@zoran-horvat Isn't the consumer also making the decision whether to create an instance of OutstandingInvoice or PaidInvoice? Or at the very least, calling the Paid method? Moreover, I would argue that under the OOP model what we have here is an invoice shifting from one state to another, not an invoice producing another invoice.
Thats the problem with OO; doing it 'right' usually isn't worth it. The cost is so high you need a massive return to justify it. That just doesn't happen in most business software.
@@zevspitz8925 The consumer does choose which class to instantiate. But don't forget that one such consumer is the ORM, which instantiates objects without knowing what it did and, on top of that, retains the reference to the object as a reference source for subsequent updates - updates that would never happen to that object! Regarding the last note, don't mix semantic with syntax. Semantically, the invoice changes. Syntactically, it does not, but rather causes a new object creation.
Awesome video (as always)! I credit you to my increasingly growing interest of applying Functional patterns in C# after I watched your PluralSight course. Do you have example code that you could show a solution that is leveraging an ORM like EF for data persistence with the functional record types that accounts for the runtime type change? I'm trying to get my mind around the layer/plumbing needed to keep those to approaches in sync.
In Smalltalk, where the term object-oriented was coined, every object has a method "become" which allows an object to become any other object of any class. I don't like OOP but it is not true that in OOP objects cannot become other objects.
That crossed my mind when I was preparing this video, but I opted not to go that far (for the sake of the younger viewers). The problem is in how objects are managed in memory, which is driven by compiler considerations and whatnot, and so the situation in OO languages today is that they do not support this operation. Hence the State pattern as an attempt to work around the problem, but with added problems of its own.
I'm just spit balling here, but - when using EF Core - you would have to add these classes using a discriminator anyway, which in turn would create a discriminator column. I think it should, in theory at least, be possible to implicitly create a PaidInvoice from an Invoice, if you map the Dirscriminator as a property to your Invoice class and change it to "PaidInvoce". The change tracker would track that change and you can just call SaveChangesAsync. When you query that same entry again, you should get a PaidInvoice back from EF Core. Haven't tried it, but if it does, that would be very hackish I'd have to admit.
Actually, no, and I think I have explained that in the video. You cannot persist state that is a function of time, because time passes even when your application does not work. All you can do is persist facts, and then reconstruct the state at a given time, which could be the current time, but does not have to be.
So the reason you give for oop being unsuitable for this use case is that you would have to manage persistence more manually since ORMs can't track changes in type, but at the end the alternative presented still has the same problem. The functional approach is more succint yes, but it doesn't allow for anything else than what the oop approach already allows.
No, that is not what I explained. Persistence is not the reason but an in-place change of the runtime type which is not possible in any mainstream programming language, provided default modeling style based on mutable classes. Immutable classes that are modeled in a functional style do not have that limitation.
@@zoran-horvat how do they not have that limitation? You are still just returning new objects of a different type from the Paid function just like in the oop version Objects are still not changing their runtime type. Am I missing something?
@@chiefxtrc What limitation are you talking about? I am talking about changing the runtime type of an object. A functional style method returns an object and hence it can pick the runtime type of that object by its coding. An object-oriented style method mutates the current object and returns void. Hence in OOP the runtime type remains, and in FP it may change.
@@zoran-horvat Let me see if I understand. Say you call the Paid() extension method on an object of type OutstandingInvoice. A new object of type PaidInvoice is returned. This is an entirely new object, so the original object has not changed its type, it still lives in the heap as an OutstandingInvoice, along with the new PaidInvoice object. So it's two separate objects, none of which has ever changed its type. Is this not correct?
@@chiefxtrc Actually, in a functional design, you would leave the first object to the garbage collector because it is now outdated. There would be no references to it, but only to the new object. That is how we normally write any function in FP - as a chain of calls. Any value we may be starting with and any intermediate result is instantly forgotten about. All we keep is the last object in the chain.
FP solution still changes the reference. If you have a List for example, and you want to change the type of one of its elements, without actually changing the list, you can't, even with FP. At least not with this approach.
You wouldn't use the List type in an FP solution, but an ImmutableList. That solves the problem because all immutable collections are designed to support nondestructive mutation.
This definitely an issue I have come across many times. The question in my mind is when will the ORM (or other persistence helpers) catch up with the FP approach. Right now if you are using EF the simplest and pragmatic approach would be to have the Invoice table have a nullable PaidOn column and let your C# model represent it that way too. This is not great OOP let alone FP. In general relation databases are themselves not great at representing an entity that has a life cycle. Only fields that should not be null at the beginning of that entities life can be declared not null. However, in reality the entity evolves and in other points of its life multiple fields can become mandatory (in fact the fact they are set at all indicates the entity's new "run time type" e..g a view on the invoice table where PaidOn IS NOT NULL becomes the PaidInvoice view).
This model, OOP or FP all the same, can end up in a single database table with a nullable column. The translation between the database row and multi-type or multi-class system, both ways, happens on transition across the application-database boundary.
@@zoran-horvat Yeah I get that but you have to give up on EF loading directly models right? EF only supports a one-to-one map from db entity to model. IOW we have more plumbing to build because currently the ORMs out there aren't quite what we need them to be.
@nicknelson5372 "type-checking to see which ones are Paid/Unpaid" That would be nice but that can't be done as far as I'm aware with the current state of EF. One table -> One type in a db context. What would be nice is to be able declare some mapping to a discriminated union and a table. Then be able to specify some rules about the table data that the mapper can use to determine exactly which type to construct. However if we were to go fully FP we would want a smooth way to let EF know "hey that immutable record instance is now replaced with this immutable record" with any of the types in the union being allowed.
Thank you so much for another video. If I understending correctly the branching with switch pattern is mainly for benefit of consumer, say, for this example a view model, who can now more elegantly render appropiate html base on the type from branching instead of mess of if and else or I am mixing it with discriminated unions which a bit diffrent topic?
Pattern matching is the way to implement behavior in functional models, since a functional model is based on types and subtypes that have no virtual methods as in a corresponding object-oriented model. Any operation would then be implemented as a function which handles all su types of its argument type. A larger portion of behavior is then implemented via function composition, e.i. a chain of calls made to individual functions, each function alone doing one and only one transformation on its inputs. When these principles are followed consistently through the application, the overall impression will be that the code is shorter and more streamlined in what it does. You have also struck an important benefit from functional design when you mentioned UI. With behavior specified separately from the types, you can implement each piece of behavior at the place where it naturally belongs, for example in the UI if that is the interface logic.
Hi Zoran. I really appreciate your videos. Thanks for taking the time to make them. I learned a lot from you. I agree with you that FP has many advantages over OOP when building a solid state machine. But in my experience, a state machine based on types is hard to persistent using an ORM. You can of course use a NoSQL document database but that is not an option for us. Instead, we use a state property and define the set of valid state transitions. The setter of the state property checks that the transition is valid and throws an exception otherwise. I am aware that this is not nearly as solid as your FP suggestion but it works and it is simply to model the state property with an ORM. How would you persist your record example with an ORM like NHibernate or Entity framework?
I Can relate with everything you said. I usually map records to an entity or to fields I side an entity to persist. EF Core has no proper support for them yet. Actually, immutable models already require explicit synchronization with the DbContext, so persisting states remains a lesser issue.
imagine as if it's the c tagged union. you can either store an int tag and a pointer to another memory location (table) or a tag along with data in a same memory chunk (it is possible to use blobs + serialization-deserialization to pull this off). of course, both techniques can be used simultaneously, just like in c unions. if you don't have that many tables and records, you can try to be smart about it and encode table number into the primary key (eg, first 8 bits could be the table number, rest -- the key itself), so that table could be derived from key without any db lookup.
PaidOn looks like a prop that should be in any invoice. Or, you could have an property that indicates the status on every invocie. Do people exist who just have to create different types in such cases, instead of adding a few more props?
@@DavidSmith-ef4eh Right. Then, what would the type of that property do? Ah yes, you will do the thing from the video in THAT type! Do people exist who just don't see one line of code further away from their current thought?
Also, I read your ideas about branching logic in other replies. But is it really a problem? Do we need to make programming so simple that people with no brains can do it? I mean, if you have an invoce, your own brain should tell you that the invoce could possibly have different statuses and that you should handle them. Does the compiler really need to prevent you from being stupid in such cases?
@@DavidSmith-ef4eh Then let me explain: "Do people exist who just have to create different types in such cases, instead of adding a few more properties." Did you not start your comment by talking down to me? And yet, your suggestion doesn't work, which makes it plain stupid. Nevertheless, you can't not see the insult with which you have started the conversation.
Development fundamental, ERD and DFD as a Starting point before coding, will reduce some of the complexities related OOP and domain knowledge required.
In many business applications, E-R analysis is putting emphasis on the wrong aspect of the problem. A better solution is very often the one that puts more focus on rules and compositions than on entities, i.e. the one that better captures the dynamic nature of processes than their static representation. That is why ER analysis and design were largely abandoned about twenty years ago. Some derivatives appeared in the later years, but none could resolve the intrinsic problem and hence none of them caught up.
My opinion is that it is changing the language in the way that the language evolution itself will not follow. It is obtrusive and oftentimes verbose, in places where there is no syntactic support for what it does. I am trying not to go against the language in programming. My code will utilize what the language is offering, but rarely more than that.
@@zoran-horvat thanks for the answer! if the language will not follow these concepts, do you mind switching to another language that has more fp features? i've seen your presentation about F# long time ago
@@benqbenq If you want a proper functional design, then F# should be the first choice. F# is a wonderful language, and it produces very short and effective code compared to C# counterpart. The advantage of C# is in the powerful combination of OOP and FP. I am just following the advancement of C# and trying to take the most out of it.
I feel like functional would be better suited to an event store than a relational database with mutable records. There is no such thing as a paid invoice, but there is an invoice and a paid event with a date. Data should be immutable all the way down.
Actually, there are both, but you are right that the payments are the principal source of information in such a system. The state of an invoice is calculated from payments, with part of that calculation typically being persisted as well (a.k.a. accounting).
Another question, why do you write Paid method in FP example as an extension method instead of a member of base Invoice type? Just because it looks good in this isolated state, or is there a technical (C# or compiler-wise) requirement that I miss to see?
The Paid method is following the fundamental principle of functional programming that types and behavior are defined separately. The principal advantage of that design paradigm is that each piece of behavior can be implemented at the place (layer, component, service, etc.) where it naturally belongs. On top of that, behavior would have access to specific elements it depends on, which might be inaccessible from the place where the types are defined. Opposite to that, an OOP design would force behavior along with the data, making extensibility possible mostly through subclassing. History is demonstrating that the former approach is better adapted to the needs of modern business applications.
@@zoran-horvat Thank you for the detailed answer again. As a long-time experienced programmer and software engineer, I myself, don't agree with your statement of "... the former approach is better ..." but, as I mentioned in my other comment, I respect your C# and programming experience greatly and, in my life, there have been many cases that I saw my mistakes which led to changing my knowledge and habits. For example, I was super against using Linq in C# long long time ago and that habit of mine changed for good many years ago. So..., for me to have a better understanding, you as an experienced OOP programmer, how and when was your transition into this approach (extension methods over type members) ? I mean how did you decide to switch?
@@XtroTheArctic That is not the switch. I use both approaches, each in its place. Virtual members are the trait of OOP. Extension methods (in C#, in particular) are the trait of FP. Take LINQ as a good example - it is hardcore functional, to the point that it does not have, and will probably never have, a method such as Do. Consequently, LINQ is implemented via extension methods.
HI Zoran, I do love these little snippet courses. I've been a fan of your style of teaching for many years through Pluralsight, first time to your channel though. I have used some of the functional programming styles from your courses on Pluralsight, particularly removing primitive obsession and immutable types. I have question regarding the PaidInvoice example, why would you have Paid method on already paid invoice that updates the date on which it was paid, surely that's wrong or am I missing something?
In that example I thought of a possibility of updating the date while retaining the state of the invoice. But that logic is also flawed. If you want something to really think about, then the invoice should not know its status. There would have to be evidence of payments, and payment status of the invoice would be a calculated value, determined by summing up the payment evidence associated with that invoice. Even the association between payments and invoices is a tricky matter, oftentimes regulated by law.
@@zoran-horvat I hope you didn;t think I was criticising, as I wasn't, I was merely clarifiying my understanding, so thank you for that. I appreciate how hard it is to think of contextual examples without over complicating the issues. Thanks for all you great work.
@@chrisbaker5284 I didn't take it as a criticism. I wanted to clarify that this design is too simple to be correct. There could be a handful of complaints to it, each perfectly reasonable. It is indeed hard to come up with a realistic business example that can fit a 10-minute explanation...
I'm not sure I understand the problem. Is it that having a nullable datetime in the base class would be undesirable because you'd then have check if it's null for other logic such as hiding/showing the button?
Precisely! The entire software would be implemented in terns of null checks, with every method implementing two operations rather than one from its name. Worse yet, if you add one more nullable field, that becomes 3-4 operations per method. Add one more and each method in the code base will implement 5, 6,... 8 distinct behaviors. That quickly becomes hell.
@@zoran-horvat Ah right. Interesting stuff that I have not thought about before since I have only coded in an OOP style. Without looking at this video, I'd make the datetime nullable and just have an expression-bodied member that does a null check. Then just have a method to set the date paid in the base class. For the UI I'd usually have have the UI list bound to an observablecollection and depending on the UI framework, have a converter to display the paid button. Honestly, the overhead for the types of applications I've worked on would be very little, but this video was very interesting to think about. For instance if I were to write a chess engine, this would be a deadly sin. Cheers for the video and for helping explain.
This is coming from a beginner, but why have separate classes for each status of the object, instead of just having one class and have the status (paid, unpaid, etc) be a property?
In that case, it would be the caller's responsibility to interpret the meanings of these properties. With explicitly modeled types, the caller only needs to ensure that it possesses a certain interface. Any subsequent operation proposed by that interface must be safe and valid, so there is nothing the caller needs to do other than its own duties. That is the principle I often emphasize: If you have an object, then it's fine.
Personally I'm a fan of functional programming. But I wonder, why do OOP people consider anemic domain model (which is what you show here) to be an antipattern?
Because their 'encapsulation' demands it. Mutation is a bad idea; unmanaged mutation is a slow-motion death sentence. OO thinks it works if you just define the boundaries right to make the mutation 'atomic' and 'well formed'. The problem is those boundaries change and are 'viral' so if you get it wrong, you are seriously screwed. FP simply decided "Mutation is *so bad it just can't be done right at scale. So don't"
If you *really* drink the kool-aid everything can be solved by types. Imagine division: `public Integer divide(Integer numerator, NotZeroInteger denominator)` So now the types are used for correctness; its on the caller to make sure to pass valid objects and the implementation is very elgant/obvious. Well of course it is, you left all the actual work to every single caller of your lib you lazy scrub.
@@adambickford8720 That makes sense. OOP often has a method on the model, that needs to set multiple fields ("atomic" operation) to have a valid state. What's the functional equivalent to that?
@@chris-pee In FP you have a function that returns a new struct in the proper state, likely copied from the initial state. Basically, how strings work. That means that the object isn't changing to other code referencing it (though that, in general, should be minimized via scoping either way) Some languages/apis make immutability easier, check out 'persistent collections' (has nothing to do with databases) for an example. Some languages have a 'with' or 'copy' function/keyword that allows you to update values w/o having to manually map the static ones.
OOP people do not have mathematical or any sort of foundational principles that they are following. They are salespeople, not mathematicians like the people coming up with functional programming.
Just add a Settlement class into the Invoice which contains the data needed to describe the settlement. Settlement can be related to Invoice in an ORM. I think you're over complicating the problem.
Settlement would exhibit all the traits outlined in the video, but yes, that would be the way to go. Though, not the way to remove complexity, as I said. The complexity will remain.
@@zoran-horvat I'm not sure why you would have Invoice, OutstandingInvoice and PaidInvoice when there is only one entity and its state is either paid or not paid. I'm on board with functional programming but returning a new Invoice object with a mutated state seems redundant. You can just update the state on the original object and pass that back to an ORM. It literally is one state or the other. In an event based system having non mutable objects is invaluable of course.
@@edgeofsanitysevensix It is not paid or not paid, to begin with. Nor can you just update the original object and pass it back to the ORM, because the object exposes behavior that depends on the status it represents, and that is not just one class. Could it be that you forgot three out of five states and all the methods when you thought you saw a simpler model?
I'm confused. In another video, you argue against inheritance, yet in your example you use it to represent paid vs outstanding invoices. Shouldn't it use a compositional design instead? For instance, you could use a Paid optional which is unwrapped to retrieve whether it's been paid or not, and if so, retrieve its payment date? The only issue I see here is the complexity of querying a list of invoices based on its payment status or payment date, which requires unwrapping the optional for each of the invoices.
I am not advocating against a single level of inheritance in any of my videos, i.e. inheritance over a single decision. How else could we create a composition if components are not polymorphic? However, you are right that this particular design would benefit from composition. Inheritance-based kinds of invoices would start showing their limitations soon after this stage.
Is this really a restriction on oop? Is there a rule in oop that you cannot change the varient of an object? Discriminated unions would solve this problem while maintaining the oop style.
Discriminated unions are not the answer because they do not support dynamic dispatch - they are not object-oriented by definition. But your question about the limitation is a good one. The truth is that OOP alone does not forbid changing the runtime type of an object, but such an operation would impose a performance penalty on all other operations, due to the change in object layout. That is why statically typed languages are so widespread today, and the reason why this kind of an operation is not supported in any of them.
@@zoran-horvat one example is the rust option enum and it's take method. It returns the owned value and changes the runtime behavior of the source option by replacing it with the none varient. This is all statically dispatched only the branch which is taken is dependent on the runtime type of the object. Making it a cheaper more restricted form of polimorphism.
@@redcrafterlppa303 Rust enums and similar types in other languages are polymorphic, but do not support inheritance - you cannot extend them in another module. Therefore, they do not qualify as object-oriented. Also, functions that return the value do not qualify as type-changing entities when the original object remains. Someone has mentioned LISP as an example where (in CLOS) one can truly change the object's runtime type during its lifetime. But imagine the cost and constraints...
@@zoran-horvat you're right, they likely don't qualify as object oriented but they are type changing. fn take(&mut self) -> T { ... *self = Option::None; ... } The function is mutating the memory at it's "this" pointer changing the type varient of the object the function got called with. You can reassign this in rust functions which is not possible in java or c# and in many others.
Hmm. I think the original problem was a bit of contrived. I would argue the data model was already wrong. Payments is something I would prefer to solve with an event-sourced approach - every data structure immutable. Of course there may be "dynamic" data structures that are just aggregates of multiple such events. But a mutable invoice? No.
@@zoran-horvat Sure. You're suggesting to use immutable data structures yourself and that the ORM has to be designed in a way to cope with that. Immutable data structures are good. Relying on an object's identity for functionality could even be considered a design smell, breaking encapsulation in the strictest sense.
ORM already maintains a DTO for its own operation's sake, including the mapping from and to the object model. Adding one more DTO on top (a.k.a. the persistence model) is one more superfluous data reshaping operation, and that is the defeat of the ORM idea. That is far from ideal. Just consider how much code you would have to add to the code base to support that additional mapping.
Ugh.. when I think about all the old code I had to manage that simply copied data from an Enity, to a DTO, to some Model object to finally be rendered.
@@zoran-horvat I always thought that the service layer (i.e. the thing consuming the ORM/repository) is the thing that owns the DTO and does the mapping of the entity coming out of the repository. This is how all of our code is written. The presentation layer then consumes the service layer's DTO. The presentation layer does not re-map the DTO to its own DTO. Why is the ORM/Repository layer providing its own DTO beneficial? Have we we been doing this wrong all along? 😅
@@Wordsalad69420 In complex cases, implementing a separate persistence model is the only way, so you were probably right - unfortunately. I say unfortunately because it was the promise of ORMs to do the mapping. Don't forget that an ORM is already performing a mapping inside, so implementing another model between that and the domain model is not adding a persistence model - it is adding a second persistence model. The root cause for this need, in EF Core in particular, is that it doesn't support multi-column and multi-table mappings. All it can do is map a single column per expression. The upside is that you can still do pretty complex things without a persistence model,provided that you can split the domain model into isolated submodels as in DDD. What I am seeing recently is that EF Core is evolving in the direction of supporting more and more DDD modeling out of the box. That might render persistence models unneeded in most cases, just as we always wanted ORMs to be.
class Invoice { prop State: Paid/Oustanding} => problem solved. The original design is bad.. You used the type of classes to representing the state of the Invoice, so it is awkward to mutate this information. If you created a normal property "State" for Invoice then everything becomes normal OOP
I don't understand why people think using inheritance as crazy is what OOP is about. There is nothing wrong with adding a paidDate attribute to the class.
To the base class? Well, if that is the idea, then let me tell you what is wrong with that - the operations would then.move to the consumer rather than staying in the producer. When done consistently, that ruins the entire design.
@nicknelson5372 I used it in terms of a feature, which has a producing and a consuming end. A consumer should not vary, i.e. branch, depending on conditions in the producer - that is indicative of procedural design, which quickly becomes unmaintainable. In a proper design, the variation is managed at the producing end. The consumer receives a unified subject to work on. That is a polymorphic object in OOP or an injected function in FP, but either way the question "what that object/function is" is resolved before control is passed to the consumer. You can come to the same conclusion by following different design methods. For instance, observing the SRP leads to the same ideas. Once popular CRC cards would enforce this model, too.
@@zoran-horvat why would you move that operation outside the invoice class? In the video, you say that you have to remove that attribute because of the single responsibility principle. And first of all that is a SOLID principle, not all OOP programmers follow those principles all the time. and second, even if you do, composition must always be chosen over inheritance. In that case, you can add a Pay object as an attribute.
Having a separate class for each logical subtype of a type is an extraordinarily poor approach which will lead to brittle and difficult to maintain code.
It depends on the circumstances. I operate on the KISS principal -- Keep It Simple Stupid. If you have a lot of subtypes that have minor differences, just collapse them into the supertype. Use a "type" property to manage what is valid and needed by each instance. If your subtypes have very large differences, you might have modeled the system incorrectly because these might not really be subtypes in the business domain. For example, I literally could make everything a subtype of "entity", but that wouldn't be very helpful. Again, it all depends on the circumstances. The goal of good software design is that someone else should be able to come along in the future, look at your code, easily understand it, and change it. Elegant design, overbearing "architecture", and object-oriented purity is often at odds with this goal. My two cents.
@@ericblankenburg You are advocating so-called poor man's polymorphism, then. The irreparable issue with that approach is that domain-related logic is implemented at the calling place, rather than at the place which is serving the objects. That leads to enormous code duplication in large systems, but also creates tons of nonmaintainable code because implementation is misplaced by design. That approach remains valid in simple cases - that explains the success of Go. But one should not attempt it in enterprise-grade software development. Fun-fact: object-oriented programming was invented (as a measure of coding discipline at first) within procedural languages more than half a century ago to deal with consequences of what you have just proposed today.
@@zoran-horvat - I am not advocating duplicating any code, nor am I advocating implementing domain-related logic in the caller, whatever that means. I am advocating KISS -- keep it simple stupid. I've been architecting, designing, and building software products and solutions for over 30 years, for some of the biggest companies on the planet, including Microsoft and IBM. I've seen it all, from big balls of mud which had individual classes with thousands of lines of code to over-architected systems that had 9 "layers" of code and multiple data-transformations between the UI and the database. The thing that actually works is KISS. The goal should be to keep the system simple, and easy to understand, which makes it easier to maintain and enhance.
@@ericblankenburg Poor OO design doesn't make procedural design any more powerful than it is. Also, such 9-layer designs are addressing problems of a great scale where procedural design doesn't even apply. It is not comparable. This video is showcasing a stripped-down part of the accounting part of a large software. 1% of it. Would you dare use tagged types in financial calculations of a versatile business? On top of my head I know of at least three requirements where that approach would fail miserably on code from the demo.
Your model allows for the same invoices to be paid multiple times. That's really weird behavior. I guess you're getting way too clever. Just put an invoice object with a state enum on it and make the payment date and other optional attributes nullable. It isn't rocket science. If you want to go with your way of OOP + ORM, I would strongly advise for separating the domain model objects from the ORM DTOs. There needs to be some kind of translation between the pure and impure parts. In fact, you can model paid and unpaid invoices as part of the domain model and supply pure functions to transition between states like you did. That way the functional part can be pure within the domain model bounds. In contrast, the database model should be kept simple like an object with a state enum and a few nullable attributes, etc. That way the database layer contains the necessary side effects to apply data changes which can be injected into the pure domain model to round up the design. I'd argue such a design is probably way too overengineered, but it depends on the circumstances. At least it's well testable. I'd go with a simple design most of the time and only put in the work if there's an actual payoff other than showcasing OOP skills.
Anyone with a degree not only "had a look" at CLOS, but also passed an exam. You could avoid sounding elitist and mention JavaScript instead. Then we could talk about why that is not the right answer (nor is that with CLOS).
@@zoran-horvat JavaScript! Keep going… You must be the only one with a degree around here. I guess that didn’t sound elitist 😂 For the the non-degree holding crowd, CLOS allows for actual object reclassing
@@drequena So, to conclude - your answer is wrong because, while CLOS is OO, OO is not CLOS. CLOS is making implementation-specific assumptions that are not, and cannot be, part of the definition of OOP. Take Rust as an example. Rust cannot implement runtime type changing due to its extreme performance-optimized view of objects. And it is still very much OO.
@@zoran-horvat so at the end of the day, you agree with me. The problem you state is not OO specific but more of a limitation of MOST OO environments, as implemented. Such thing is demonstrated by the mere existence of an OO system on which that limitation does not exist. Why such belligerence? I too tend to favor funcional programming over object orientation this days, just for different reasons
Sorry, but the OOP example is horrendous. PaidInvoice needs to be constructed with the Invoice object. Essentially just wrapping the InvoiceObject to perform the "paid" functionality. Then the implementation is clean without needing to make modification to parent/child classes. You could even modify it to allow multiple payments to the Invoice without touching any other Invoice code. Shouldn't cause any issues with ORM as well as long as you're using a dataMapper pattern instead of activeRecord. You just need to do the same thing in the dataMapper class to add the new functionality.
What you are referring to is to use object composition to represent variable state. What you have forgot to mention is that the component would also be polymorphic, and so everything said in the video moves to the payment-related component contained in the invoice.
I have witnessed many times that programmers who are incapable of writing good OO code are equally if not more incapacitated when writing functional code. What's your story?
I struggled with this issue with oop for many years but as i have learned a bit of accounting, it helped me crack the issue. If you think of invoice as an object, you feel that it has behaviour. But invoice is simply a record as you call it in functional programming. What does it record? It records the services provided or items sold or a combination of both (example your car mechanic invoice which includes labour charges and parts sold). The payment for an invoice is a separate record and needs to be tracked separately. You may be required at some point in the life of the app how the payment was done (cash, card, wire transfer etc). There may be partial payment or there may be payment of more than one invoice through single instrument like a bank cheque. The state of the invoice is two additional attributes, if it is paid and if yes on which date, come from a different data source and should not ideally map to same table. A transactional database and a reporting database are two different designs. So if you are checking only one of them. If you are checking paid status of the invoices, it's coming from reporting db and that's a read only data. The payment operation you do on the invoice goes to transactional db and it would at times take a while to reflect in reporting db. So this is not as simple problem to club all these concerns into one sample. Oop at times requires deeper thinking of the domain to solve the problem correctly.
The application from the video is not the actual accounting application.
As you have mentioned, in an actual business, we would store proofs of payments, rather than statuses. In that light, an invoice could also be partly paid, have an associated late payment interests, past dues, fees, etc.
All those details would be too much for a TH-cam video showing a technical issue in code.
The accounting ledger is the original immutable data store.
With the OOP solution, an append only dB schema solves the problem. Then you get a queriable ledger, quite often a requirement in large systems. It also solves the locking problem as we've discussed before.
But for a small application, it hardly matters.
Yes, I understood that the demo was about introducing functional concepts, I'm still thinking about the ramifications of that.
A function in a functional programming language is equivalent to a single method interface in object oriented programming. An object in object oriented programming is equivalent to a closure in functional programming. You don't really miss anything using one or the other paradigm, but ease of use will definitely be an important factor in choosing the paradigm to use. Applying SOLID principles in OOP makes your code more similar to functional code, that is small objects and small interfaces with small state and small number methods and with little or no subclasses.
I agree with everything you said, with a note that must be added for completeness. There is one large distinction, and that is the fundamental distinction between OOP and FP: objects tend to expose void methods. That design decision is backed by ORM-s and, just to name two.
The older I am, the more I dislike mutable design. This video is one in the series that underlines my preference toward immutable design, and in that case OOP and FP become equivalent in the way you have explained.
I agree with everything you said, with a note that must be added for completeness. There is one large distinction, and that is the fundamental distinction between OOP and FP: objects tend to expose void methods. That design decision is backed by ORM-s and, just to name two.
The older I am, the more I dislike mutable design. This video is one in the series that underlines my preference toward immutable design, and in that case OOP and FP become equivalent in the way you have explained.
Joe Rainsberger in one of his talks said something like: "...with well designed OOP an object is just a set of partially applied functions". I like that.
@@ddanielsandberg I am not aware of that citation, but if I may guess it is based on observation that an object may combine its private state with the arguments and in that way act identically to a partially applied function in any functional language. In that sense, I agree fully with that quote.
I haven't even heard of a record type in c#, I assumed you'd use structs for the functional approach. Time to do some more googling.
How does this solve the ORM problem? I see the same issue here. The type has changed. The object needs to update.
There is the difference. In OOP, an ORM is tracking updates. When State pattern is used, that mechanism is broken and every caller of the State object must know that.
In FP, ORM does not track updates, but it is rather used in a different operational mode. Therefore, these few classes would not be treated separately from others and their callers would have no new responsibility to implement. The whole code remains uniform with or without this feature.
@@zoran-horvat thx Zoran. frankly I fo not see where it is simpler still. You have to implement the update method both case. Dont you? Dont get me wrong, I am asking because I am learning.
@@LE8271 It would be very difficult to explain the upsides of FP in a comment. There are other comments here where I tried it in more words. I would certainly point you to learn about functional modeling and you will advance, no doubt.
The short version is that, in modern applications, one team defines the model and another team adds behavior on top of it. That is precisely how FP puts it, and exactly opposite to how OOP puts it. That is why C# is attaining more and more of the FP syntax in recent years. That method simply pays off in application development.
You can change the paid function call type to just accept OutstandingInvoice it will put responsibility on the client to pattern match the invoice and call paid function
Can you please simply explain why a nullable PaidOn property in a single Invoice type would be bad? I think it's a perfect model of a real-world invoice. Real-world invoice can be stamped with a red colored paid on this date and the null state of PaidOn property would mean the real-world invoice doesn't have the red stamp on it yet. What's wrong with this simple model? Why complicate it with multiple types like UnpaidInvoice and PaidInvoice?
As a matter of fact, I can simply explain the matter.
Adding a nullable state, as in the best tradition of procedural design of the 1980s, would require all operations (dozens of them on an important model!) to contain a branching instruction and to implement two operations each, rather than just one. Quite often, consuming code would also use branching, rendering any subsequent evolution of the underlying model painful, if not impossible. That decision would add to cyclomatic complexity of dozens of methods, double the number of unit tests for them, but also help plant a few bugs here and there, bugs that would otherwise be impossible.
The short answer is: That would be a flat design flaw. You could make it work, but it would be a flaw nonetheless.
For the end, sincere advice: Learn programming beyond nullable state; learn languages that don't even have a null, at least not the one you can use in custom code. You will see a different programming paradigm that works much better than the programming of old.
@@zoran-horvat Thank you. It's a nice explanation from your point of view but personally I don't see a big problem with implementing branching code. For many decades, millions of applications and games were written with OOP and branching code and they served millions of people and made billions of dollars so I think the idea of banishing the branching code is not necessary even if it sounds and looks beautiful. I think the complexity created by having multiple different types for a single real-world object like an Invoice is much worse than having some branching code here and there. You are one of the rare people I highly respect on TH-cam based on their programming (especially in C#) experience and I like your ideas and teachings but I guess we won't agree on this specific topic. Thank you very much!
@@XtroTheArctic I am consulting two companies right now which were thinking that same way. Millions in, and millions more. Until the cost of going forward started to burn twice that much, for the reasons I outlined above.
Doing things that way can make the code work, but only until it grinds to a halt. Programmers usually solve the problem by quitting and finding a new job, but that doesn't make the company's software work beyond the point where expenses overgrow revenues.
But it also means that we don't need several classes to design the solution. We can have one Invoice class with a propery "PaymentStatus" - can be enum as well. Yes I know it is redudant with a PaidDate? but it also can give a clean info about the invoice. Also I would not rely on a system date about payment. Also we can introduce a new class Payment wich can have Invoice reference. So that class can be used for payments. Samo kažem.
Also let not mix ORM entities with domain objects.
Oh, no, no. You have mixed up unions (including derived classes) with tagged unions. The latter opens up all sorts of issues in code.
@@zoran-horvatVladimir Horikov has an interesting opinion about class pollution where you can have a simpler solution instead of a hierarchical set of classes, which increases in complexity with each new property that will be added.
@@dxs1971 I agree with that opinion, but this is not a hierarchy of classes. A single level of inheritance is not a hierarchy.
How would you load an invoice from the database so it's the correct type? I assume it's all in one table "Invoices" of entity type Invoice, so how would you load a PaidInvoice or OutstandingInvoice?
There must be a conversion from raw database entities to proper model entities, given a time parameter. That is one of the hidden gems in this video, which is also a story of working with time. Since the status of an invoice depends on when the query is executed, it certainly cannot be stored in the database. I would only store the facts, such as events that happened at certain time.
Interesting video but I might have missed something. The idea behind the State pattern is to decouple the class from its state. Therefore shouldn't you have Invoice and an associated InvoiceState? So what you specialize is the state, not the invoice: PaidState, OutstandingState, ... The behavior of the invoice will then change as its state changes, as if the invoice changed its class.
I don't think that is the purpose of that pattern. That _could be_ the purpose in some implementations if coupling to state causes design issues, but it would be an overstatement to say that should always be the case.
I cannot remember my goals at the time of making this video, but I believe, from the outcome, that the state did not bother me too much, compared to state transitions.
Interesting! Although, I'm fairly sure that in 99% of cases, State patterns with OOP will not be used, but rather a pragmatic solution will be used. So in this case, the PaidOn? property will just be added to the base class to avoid the hassle. But it's an interesting approach nevertheless.
First, what you call pragmatic approach I call turning an object-oriented solution into a procedural one, with all the negative consequences - primarily moving operations to the consumer.
Second, while you can evade the problem in a simple case, actual cases in practice are much more complex and such a workaround would not even be available.
@@zoran-horvat Yeah, I am with you on that one. I'm also leaning towards your immutable, functional approach when necessary. The OOP state pattern feels very clunky. But I was just stating what will probably happen in practice most of the time 😄
@@zoran-horvat Isn't the consumer also making the decision whether to create an instance of OutstandingInvoice or PaidInvoice? Or at the very least, calling the Paid method?
Moreover, I would argue that under the OOP model what we have here is an invoice shifting from one state to another, not an invoice producing another invoice.
Thats the problem with OO; doing it 'right' usually isn't worth it. The cost is so high you need a massive return to justify it. That just doesn't happen in most business software.
@@zevspitz8925 The consumer does choose which class to instantiate. But don't forget that one such consumer is the ORM, which instantiates objects without knowing what it did and, on top of that, retains the reference to the object as a reference source for subsequent updates - updates that would never happen to that object!
Regarding the last note, don't mix semantic with syntax. Semantically, the invoice changes. Syntactically, it does not, but rather causes a new object creation.
Awesome video (as always)! I credit you to my increasingly growing interest of applying Functional patterns in C# after I watched your PluralSight course.
Do you have example code that you could show a solution that is leveraging an ORM like EF for data persistence with the functional record types that accounts for the runtime type change? I'm trying to get my mind around the layer/plumbing needed to keep those to approaches in sync.
I plan to make a video on that topic in the near future.
@@zoran-horvat thank you! Cant wait, you are my C# FP hero!
@@zoran-horvat Yes, please! 🙂
@@zoran-horvat just wondering... is this video (ORM and FP) ever made? Can you please point us to the video?
In Smalltalk, where the term object-oriented was coined, every object has a method "become" which allows an object to become any other object of any class. I don't like OOP but it is not true that in OOP objects cannot become other objects.
That crossed my mind when I was preparing this video, but I opted not to go that far (for the sake of the younger viewers).
The problem is in how objects are managed in memory, which is driven by compiler considerations and whatnot, and so the situation in OO languages today is that they do not support this operation. Hence the State pattern as an attempt to work around the problem, but with added problems of its own.
@@zoran-horvat Thank you for the reply and I agree with your analysis.
@@jboss1073 I forgot to thank you for mentioning that detail - I am sure that will help viewers see the problem better.
@@zoran-horvat You are welcome. I'm happy to remind people of Smalltalk.
I'm just spit balling here, but - when using EF Core - you would have to add these classes using a discriminator anyway, which in turn would create a discriminator column. I think it should, in theory at least, be possible to implicitly create a PaidInvoice from an Invoice, if you map the Dirscriminator as a property to your Invoice class and change it to "PaidInvoce". The change tracker would track that change and you can just call SaveChangesAsync. When you query that same entry again, you should get a PaidInvoice back from EF Core. Haven't tried it, but if it does, that would be very hackish I'd have to admit.
Actually, no, and I think I have explained that in the video. You cannot persist state that is a function of time, because time passes even when your application does not work.
All you can do is persist facts, and then reconstruct the state at a given time, which could be the current time, but does not have to be.
So the reason you give for oop being unsuitable for this use case is that you would have to manage persistence more manually since ORMs can't track changes in type, but at the end the alternative presented still has the same problem. The functional approach is more succint yes, but it doesn't allow for anything else than what the oop approach already allows.
No, that is not what I explained. Persistence is not the reason but an in-place change of the runtime type which is not possible in any mainstream programming language, provided default modeling style based on mutable classes.
Immutable classes that are modeled in a functional style do not have that limitation.
@@zoran-horvat how do they not have that limitation? You are still just returning new objects of a different type from the Paid function just like in the oop version Objects are still not changing their runtime type. Am I missing something?
@@chiefxtrc What limitation are you talking about? I am talking about changing the runtime type of an object. A functional style method returns an object and hence it can pick the runtime type of that object by its coding. An object-oriented style method mutates the current object and returns void. Hence in OOP the runtime type remains, and in FP it may change.
@@zoran-horvat Let me see if I understand. Say you call the Paid() extension method on an object of type OutstandingInvoice. A new object of type PaidInvoice is returned. This is an entirely new object, so the original object has not changed its type, it still lives in the heap as an OutstandingInvoice, along with the new PaidInvoice object. So it's two separate objects, none of which has ever changed its type. Is this not correct?
@@chiefxtrc Actually, in a functional design, you would leave the first object to the garbage collector because it is now outdated. There would be no references to it, but only to the new object. That is how we normally write any function in FP - as a chain of calls. Any value we may be starting with and any intermediate result is instantly forgotten about. All we keep is the last object in the chain.
FP solution still changes the reference. If you have a List for example, and you want to change the type of one of its elements, without actually changing the list, you can't, even with FP. At least not with this approach.
You wouldn't use the List type in an FP solution, but an ImmutableList. That solves the problem because all immutable collections are designed to support nondestructive mutation.
i think lesson about how to live with FP approach and EfCore would be great
I have that topic in the queue. There will be a video about that in the future.
This definitely an issue I have come across many times. The question in my mind is when will the ORM (or other persistence helpers) catch up with the FP approach. Right now if you are using EF the simplest and pragmatic approach would be to have the Invoice table have a nullable PaidOn column and let your C# model represent it that way too. This is not great OOP let alone FP.
In general relation databases are themselves not great at representing an entity that has a life cycle. Only fields that should not be null at the beginning of that entities life can be declared not null. However, in reality the entity evolves and in other points of its life multiple fields can become mandatory (in fact the fact they are set at all indicates the entity's new "run time type" e..g a view on the invoice table where PaidOn IS NOT NULL becomes the PaidInvoice view).
This model, OOP or FP all the same, can end up in a single database table with a nullable column. The translation between the database row and multi-type or multi-class system, both ways, happens on transition across the application-database boundary.
@@zoran-horvat Yeah I get that but you have to give up on EF loading directly models right? EF only supports a one-to-one map from db entity to model. IOW we have more plumbing to build because currently the ORMs out there aren't quite what we need them to be.
@nicknelson5372 "type-checking to see which ones are Paid/Unpaid" That would be nice but that can't be done as far as I'm aware with the current state of EF. One table -> One type in a db context.
What would be nice is to be able declare some mapping to a discriminated union and a table. Then be able to specify some rules about the table data that the mapper can use to determine exactly which type to construct.
However if we were to go fully FP we would want a smooth way to let EF know "hey that immutable record instance is now replaced with this immutable record" with any of the types in the union being allowed.
Thank you so much for another video.
If I understending correctly the branching with switch pattern is mainly for benefit of consumer, say, for this example a view model, who can now more elegantly render appropiate html base on the type from branching instead of mess of if and else or I am mixing it with discriminated unions which a bit diffrent topic?
Pattern matching is the way to implement behavior in functional models, since a functional model is based on types and subtypes that have no virtual methods as in a corresponding object-oriented model.
Any operation would then be implemented as a function which handles all su types of its argument type.
A larger portion of behavior is then implemented via function composition, e.i. a chain of calls made to individual functions, each function alone doing one and only one transformation on its inputs.
When these principles are followed consistently through the application, the overall impression will be that the code is shorter and more streamlined in what it does.
You have also struck an important benefit from functional design when you mentioned UI. With behavior specified separately from the types, you can implement each piece of behavior at the place where it naturally belongs, for example in the UI if that is the interface logic.
Hi Zoran.
I really appreciate your videos. Thanks for taking the time to make them. I learned a lot from you.
I agree with you that FP has many advantages over OOP when building a solid state machine. But in my experience, a state machine based on types is hard to persistent using an ORM. You can of course use a NoSQL document database but that is not an option for us. Instead, we use a state property and define the set of valid state transitions. The setter of the state property checks that the transition is valid and throws an exception otherwise. I am aware that this is not nearly as solid as your FP suggestion but it works and it is simply to model the state property with an ORM.
How would you persist your record example with an ORM like NHibernate or Entity framework?
I Can relate with everything you said. I usually map records to an entity or to fields I side an entity to persist. EF Core has no proper support for them yet. Actually, immutable models already require explicit synchronization with the DbContext, so persisting states remains a lesser issue.
imagine as if it's the c tagged union. you can either store an int tag and a pointer to another memory location (table) or a tag along with data in a same memory chunk (it is possible to use blobs + serialization-deserialization to pull this off). of course, both techniques can be used simultaneously, just like in c unions.
if you don't have that many tables and records, you can try to be smart about it and encode table number into the primary key (eg, first 8 bits could be the table number, rest -- the key itself), so that table could be derived from key without any db lookup.
PaidOn looks like a prop that should be in any invoice. Or, you could have an property that indicates the status on every invocie. Do people exist who just have to create different types in such cases, instead of adding a few more props?
@@DavidSmith-ef4eh Right. Then, what would the type of that property do? Ah yes, you will do the thing from the video in THAT type! Do people exist who just don't see one line of code further away from their current thought?
Also, I read your ideas about branching logic in other replies. But is it really a problem? Do we need to make programming so simple that people with no brains can do it? I mean, if you have an invoce, your own brain should tell you that the invoce could possibly have different statuses and that you should handle them. Does the compiler really need to prevent you from being stupid in such cases?
@@DavidSmith-ef4eh Keep going and don't worry. 70% of programmers never get that.
@@zoran-horvat it's better to not reply, than to insult your audience. But you do you.
@@DavidSmith-ef4eh Then let me explain:
"Do people exist who just have to create different types in such cases, instead of adding a few more properties."
Did you not start your comment by talking down to me? And yet, your suggestion doesn't work, which makes it plain stupid. Nevertheless, you can't not see the insult with which you have started the conversation.
Development fundamental, ERD and DFD as a Starting point before coding, will reduce some of the complexities related OOP and domain knowledge required.
In many business applications, E-R analysis is putting emphasis on the wrong aspect of the problem. A better solution is very often the one that puts more focus on rules and compositions than on entities, i.e. the one that better captures the dynamic nature of processes than their static representation.
That is why ER analysis and design were largely abandoned about twenty years ago. Some derivatives appeared in the later years, but none could resolve the intrinsic problem and hence none of them caught up.
whats your opinion on language-ext lib?
My opinion is that it is changing the language in the way that the language evolution itself will not follow. It is obtrusive and oftentimes verbose, in places where there is no syntactic support for what it does.
I am trying not to go against the language in programming. My code will utilize what the language is offering, but rarely more than that.
@@zoran-horvat thanks for the answer! if the language will not follow these concepts, do you mind switching to another language that has more fp features? i've seen your presentation about F# long time ago
@@benqbenq If you want a proper functional design, then F# should be the first choice. F# is a wonderful language, and it produces very short and effective code compared to C# counterpart.
The advantage of C# is in the powerful combination of OOP and FP. I am just following the advancement of C# and trying to take the most out of it.
I feel like functional would be better suited to an event store than a relational database with mutable records. There is no such thing as a paid invoice, but there is an invoice and a paid event with a date. Data should be immutable all the way down.
Actually, there are both, but you are right that the payments are the principal source of information in such a system. The state of an invoice is calculated from payments, with part of that calculation typically being persisted as well (a.k.a. accounting).
Another question, why do you write Paid method in FP example as an extension method instead of a member of base Invoice type? Just because it looks good in this isolated state, or is there a technical (C# or compiler-wise) requirement that I miss to see?
The Paid method is following the fundamental principle of functional programming that types and behavior are defined separately. The principal advantage of that design paradigm is that each piece of behavior can be implemented at the place (layer, component, service, etc.) where it naturally belongs. On top of that, behavior would have access to specific elements it depends on, which might be inaccessible from the place where the types are defined.
Opposite to that, an OOP design would force behavior along with the data, making extensibility possible mostly through subclassing.
History is demonstrating that the former approach is better adapted to the needs of modern business applications.
@@zoran-horvat Thank you for the detailed answer again. As a long-time experienced programmer and software engineer, I myself, don't agree with your statement of "... the former approach is better ..." but, as I mentioned in my other comment, I respect your C# and programming experience greatly and, in my life, there have been many cases that I saw my mistakes which led to changing my knowledge and habits. For example, I was super against using Linq in C# long long time ago and that habit of mine changed for good many years ago. So..., for me to have a better understanding, you as an experienced OOP programmer, how and when was your transition into this approach (extension methods over type members) ? I mean how did you decide to switch?
@@XtroTheArctic That is not the switch. I use both approaches, each in its place.
Virtual members are the trait of OOP. Extension methods (in C#, in particular) are the trait of FP.
Take LINQ as a good example - it is hardcore functional, to the point that it does not have, and will probably never have, a method such as Do. Consequently, LINQ is implemented via extension methods.
This is pure gold!!!!
Glad you liked it!
HI Zoran, I do love these little snippet courses. I've been a fan of your style of teaching for many years through Pluralsight, first time to your channel though. I have used some of the functional programming styles from your courses on Pluralsight, particularly removing primitive obsession and immutable types.
I have question regarding the PaidInvoice example, why would you have Paid method on already paid invoice that updates the date on which it was paid, surely that's wrong or am I missing something?
In that example I thought of a possibility of updating the date while retaining the state of the invoice. But that logic is also flawed.
If you want something to really think about, then the invoice should not know its status. There would have to be evidence of payments, and payment status of the invoice would be a calculated value, determined by summing up the payment evidence associated with that invoice. Even the association between payments and invoices is a tricky matter, oftentimes regulated by law.
@@zoran-horvat I hope you didn;t think I was criticising, as I wasn't, I was merely clarifiying my understanding, so thank you for that. I appreciate how hard it is to think of contextual examples without over complicating the issues. Thanks for all you great work.
@@chrisbaker5284 I didn't take it as a criticism. I wanted to clarify that this design is too simple to be correct. There could be a handful of complaints to it, each perfectly reasonable. It is indeed hard to come up with a realistic business example that can fit a 10-minute explanation...
@@zoran-horvat Good, I wouldn't want to offend.
is it a naïve solution to just add a property? or was the example just for illustrative purposes?
What would the property return?
I'm not sure I understand the problem. Is it that having a nullable datetime in the base class would be undesirable because you'd then have check if it's null for other logic such as hiding/showing the button?
Precisely! The entire software would be implemented in terns of null checks, with every method implementing two operations rather than one from its name.
Worse yet, if you add one more nullable field, that becomes 3-4 operations per method. Add one more and each method in the code base will implement 5, 6,... 8 distinct behaviors. That quickly becomes hell.
@@zoran-horvat Ah right. Interesting stuff that I have not thought about before since I have only coded in an OOP style.
Without looking at this video, I'd make the datetime nullable and just have an expression-bodied member that does a null check. Then just have a method to set the date paid in the base class.
For the UI I'd usually have have the UI list bound to an observablecollection and depending on the UI framework, have a converter to display the paid button.
Honestly, the overhead for the types of applications I've worked on would be very little, but this video was very interesting to think about. For instance if I were to write a chess engine, this would be a deadly sin.
Cheers for the video and for helping explain.
This is coming from a beginner, but why have separate classes for each status of the object, instead of just having one class and have the status (paid, unpaid, etc) be a property?
In that case, it would be the caller's responsibility to interpret the meanings of these properties. With explicitly modeled types, the caller only needs to ensure that it possesses a certain interface. Any subsequent operation proposed by that interface must be safe and valid, so there is nothing the caller needs to do other than its own duties.
That is the principle I often emphasize: If you have an object, then it's fine.
Personally I'm a fan of functional programming. But I wonder, why do OOP people consider anemic domain model (which is what you show here) to be an antipattern?
Because their 'encapsulation' demands it. Mutation is a bad idea; unmanaged mutation is a slow-motion death sentence. OO thinks it works if you just define the boundaries right to make the mutation 'atomic' and 'well formed'. The problem is those boundaries change and are 'viral' so if you get it wrong, you are seriously screwed.
FP simply decided "Mutation is *so bad it just can't be done right at scale. So don't"
If you *really* drink the kool-aid everything can be solved by types. Imagine division:
`public Integer divide(Integer numerator, NotZeroInteger denominator)`
So now the types are used for correctness; its on the caller to make sure to pass valid objects and the implementation is very elgant/obvious. Well of course it is, you left all the actual work to every single caller of your lib you lazy scrub.
@@adambickford8720 That makes sense. OOP often has a method on the model, that needs to set multiple fields ("atomic" operation) to have a valid state. What's the functional equivalent to that?
@@chris-pee In FP you have a function that returns a new struct in the proper state, likely copied from the initial state. Basically, how strings work. That means that the object isn't changing to other code referencing it (though that, in general, should be minimized via scoping either way)
Some languages/apis make immutability easier, check out 'persistent collections' (has nothing to do with databases) for an example. Some languages have a 'with' or 'copy' function/keyword that allows you to update values w/o having to manually map the static ones.
OOP people do not have mathematical or any sort of foundational principles that they are following. They are salespeople, not mathematicians like the people coming up with functional programming.
Just add a Settlement class into the Invoice which contains the data needed to describe the settlement. Settlement can be related to Invoice in an ORM. I think you're over complicating the problem.
Settlement would exhibit all the traits outlined in the video, but yes, that would be the way to go. Though, not the way to remove complexity, as I said. The complexity will remain.
@@zoran-horvat I'm not sure why you would have Invoice, OutstandingInvoice and PaidInvoice when there is only one entity and its state is either paid or not paid. I'm on board with functional programming but returning a new Invoice object with a mutated state seems redundant. You can just update the state on the original object and pass that back to an ORM. It literally is one state or the other. In an event based system having non mutable objects is invaluable of course.
@@edgeofsanitysevensix It is not paid or not paid, to begin with. Nor can you just update the original object and pass it back to the ORM, because the object exposes behavior that depends on the status it represents, and that is not just one class.
Could it be that you forgot three out of five states and all the methods when you thought you saw a simpler model?
I'm confused. In another video, you argue against inheritance, yet in your example you use it to represent paid vs outstanding invoices. Shouldn't it use a compositional design instead? For instance, you could use a Paid optional which is unwrapped to retrieve whether it's been paid or not, and if so, retrieve its payment date? The only issue I see here is the complexity of querying a list of invoices based on its payment status or payment date, which requires unwrapping the optional for each of the invoices.
I am not advocating against a single level of inheritance in any of my videos, i.e. inheritance over a single decision. How else could we create a composition if components are not polymorphic?
However, you are right that this particular design would benefit from composition. Inheritance-based kinds of invoices would start showing their limitations soon after this stage.
Is this really a restriction on oop?
Is there a rule in oop that you cannot change the varient of an object? Discriminated unions would solve this problem while maintaining the oop style.
Discriminated unions are not the answer because they do not support dynamic dispatch - they are not object-oriented by definition.
But your question about the limitation is a good one. The truth is that OOP alone does not forbid changing the runtime type of an object, but such an operation would impose a performance penalty on all other operations, due to the change in object layout. That is why statically typed languages are so widespread today, and the reason why this kind of an operation is not supported in any of them.
@@zoran-horvat one example is the rust option enum and it's take method. It returns the owned value and changes the runtime behavior of the source option by replacing it with the none varient. This is all statically dispatched only the branch which is taken is dependent on the runtime type of the object. Making it a cheaper more restricted form of polimorphism.
@@redcrafterlppa303 Rust enums and similar types in other languages are polymorphic, but do not support inheritance - you cannot extend them in another module. Therefore, they do not qualify as object-oriented.
Also, functions that return the value do not qualify as type-changing entities when the original object remains.
Someone has mentioned LISP as an example where (in CLOS) one can truly change the object's runtime type during its lifetime. But imagine the cost and constraints...
@@zoran-horvat you're right, they likely don't qualify as object oriented but they are type changing.
fn take(&mut self) -> T {
...
*self = Option::None;
...
}
The function is mutating the memory at it's "this" pointer changing the type varient of the object the function got called with.
You can reassign this in rust functions which is not possible in java or c# and in many others.
Hmm. I think the original problem was a bit of contrived.
I would argue the data model was already wrong. Payments is something I would prefer to solve with an event-sourced approach - every data structure immutable.
Of course there may be "dynamic" data structures that are just aggregates of multiple such events.
But a mutable invoice? No.
Why do you dismiss traditional object-oriented modeling in favor of event sourcing, like event sourcing is suitable for every problem?
@@zoran-horvat I haven't dismissed it. I dismissed this example as a suitable one.
@@cyrusol Does that mean that the conclusion stays, in a different example? Because the video is about the conclusion, not about the example.
@@zoran-horvat Sure. You're suggesting to use immutable data structures yourself and that the ORM has to be designed in a way to cope with that.
Immutable data structures are good. Relying on an object's identity for functionality could even be considered a design smell, breaking encapsulation in the strictest sense.
@@cyrusol What do you suggest instead of object identities?
Why is the ORM issue even a problem? Ideally you would have some DTO, not the actual ORM entity.
ORM already maintains a DTO for its own operation's sake, including the mapping from and to the object model. Adding one more DTO on top (a.k.a. the persistence model) is one more superfluous data reshaping operation, and that is the defeat of the ORM idea. That is far from ideal. Just consider how much code you would have to add to the code base to support that additional mapping.
Ugh.. when I think about all the old code I had to manage that simply copied data from an Enity, to a DTO, to some Model object to finally be rendered.
@@zoran-horvat I always thought that the service layer (i.e. the thing consuming the ORM/repository) is the thing that owns the DTO and does the mapping of the entity coming out of the repository. This is how all of our code is written. The presentation layer then consumes the service layer's DTO. The presentation layer does not re-map the DTO to its own DTO. Why is the ORM/Repository layer providing its own DTO beneficial? Have we we been doing this wrong all along? 😅
@@Wordsalad69420 In complex cases, implementing a separate persistence model is the only way, so you were probably right - unfortunately. I say unfortunately because it was the promise of ORMs to do the mapping. Don't forget that an ORM is already performing a mapping inside, so implementing another model between that and the domain model is not adding a persistence model - it is adding a second persistence model.
The root cause for this need, in EF Core in particular, is that it doesn't support multi-column and multi-table mappings. All it can do is map a single column per expression.
The upside is that you can still do pretty complex things without a persistence model,provided that you can split the domain model into isolated submodels as in DDD. What I am seeing recently is that EF Core is evolving in the direction of supporting more and more DDD modeling out of the box. That might render persistence models unneeded in most cases, just as we always wanted ORMs to be.
class Invoice { prop State: Paid/Oustanding} => problem solved. The original design is bad.. You used the type of classes to representing the state of the Invoice, so it is awkward to mutate this information. If you created a normal property "State" for Invoice then everything becomes normal OOP
The entire problem moves into the state class, then, and repeats its manifestation in the way depicted in the video. You solved nothing.
I don't understand why people think using inheritance as crazy is what OOP is about. There is nothing wrong with adding a paidDate attribute to the class.
To the base class? Well, if that is the idea, then let me tell you what is wrong with that - the operations would then.move to the consumer rather than staying in the producer. When done consistently, that ruins the entire design.
@nicknelson5372 I used it in terms of a feature, which has a producing and a consuming end.
A consumer should not vary, i.e. branch, depending on conditions in the producer - that is indicative of procedural design, which quickly becomes unmaintainable.
In a proper design, the variation is managed at the producing end. The consumer receives a unified subject to work on. That is a polymorphic object in OOP or an injected function in FP, but either way the question "what that object/function is" is resolved before control is passed to the consumer.
You can come to the same conclusion by following different design methods. For instance, observing the SRP leads to the same ideas. Once popular CRC cards would enforce this model, too.
@@zoran-horvat why would you move that operation outside the invoice class? In the video, you say that you have to remove that attribute because of the single responsibility principle. And first of all that is a SOLID principle, not all OOP programmers follow those principles all the time. and second, even if you do, composition must always be chosen over inheritance. In that case, you can add a Pay object as an attribute.
@@f1945 You can add Pay object as a polymorphic attribute. Everything said in the video now applies to Pay, rather than Invoice.
Like i pre gledanja 😀
Having a separate class for each logical subtype of a type is an extraordinarily poor approach which will lead to brittle and difficult to maintain code.
What do you suggest as an alternative?
It depends on the circumstances. I operate on the KISS principal -- Keep It Simple Stupid. If you have a lot of subtypes that have minor differences, just collapse them into the supertype. Use a "type" property to manage what is valid and needed by each instance. If your subtypes have very large differences, you might have modeled the system incorrectly because these might not really be subtypes in the business domain. For example, I literally could make everything a subtype of "entity", but that wouldn't be very helpful. Again, it all depends on the circumstances. The goal of good software design is that someone else should be able to come along in the future, look at your code, easily understand it, and change it. Elegant design, overbearing "architecture", and object-oriented purity is often at odds with this goal. My two cents.
@@ericblankenburg You are advocating so-called poor man's polymorphism, then. The irreparable issue with that approach is that domain-related logic is implemented at the calling place, rather than at the place which is serving the objects. That leads to enormous code duplication in large systems, but also creates tons of nonmaintainable code because implementation is misplaced by design.
That approach remains valid in simple cases - that explains the success of Go. But one should not attempt it in enterprise-grade software development.
Fun-fact: object-oriented programming was invented (as a measure of coding discipline at first) within procedural languages more than half a century ago to deal with consequences of what you have just proposed today.
@@zoran-horvat - I am not advocating duplicating any code, nor am I advocating implementing domain-related logic in the caller, whatever that means. I am advocating KISS -- keep it simple stupid. I've been architecting, designing, and building software products and solutions for over 30 years, for some of the biggest companies on the planet, including Microsoft and IBM. I've seen it all, from big balls of mud which had individual classes with thousands of lines of code to over-architected systems that had 9 "layers" of code and multiple data-transformations between the UI and the database. The thing that actually works is KISS. The goal should be to keep the system simple, and easy to understand, which makes it easier to maintain and enhance.
@@ericblankenburg Poor OO design doesn't make procedural design any more powerful than it is. Also, such 9-layer designs are addressing problems of a great scale where procedural design doesn't even apply. It is not comparable.
This video is showcasing a stripped-down part of the accounting part of a large software. 1% of it. Would you dare use tagged types in financial calculations of a versatile business? On top of my head I know of at least three requirements where that approach would fail miserably on code from the demo.
hvala zoki
Your model allows for the same invoices to be paid multiple times. That's really weird behavior.
I guess you're getting way too clever. Just put an invoice object with a state enum on it and make the payment date and other optional attributes nullable. It isn't rocket science.
If you want to go with your way of OOP + ORM, I would strongly advise for separating the domain model objects from the ORM DTOs. There needs to be some kind of translation between the pure and impure parts.
In fact, you can model paid and unpaid invoices as part of the domain model and supply pure functions to transition between states like you did. That way the functional part can be pure within the domain model bounds. In contrast, the database model should be kept simple like an object with a state enum and a few nullable attributes, etc. That way the database layer contains the necessary side effects to apply data changes which can be injected into the pure domain model to round up the design.
I'd argue such a design is probably way too overengineered, but it depends on the circumstances. At least it's well testable. I'd go with a simple design most of the time and only put in the work if there's an actual payoff other than showcasing OOP skills.
Or you could have a look at CLOS. Of course that one's about generic functions. Still OOP..
Anyone with a degree not only "had a look" at CLOS, but also passed an exam.
You could avoid sounding elitist and mention JavaScript instead. Then we could talk about why that is not the right answer (nor is that with CLOS).
@@zoran-horvat JavaScript! Keep going…
You must be the only one with a degree around here. I guess that didn’t sound elitist 😂
For the the non-degree holding crowd, CLOS allows for actual object reclassing
@@drequena So, to conclude - your answer is wrong because, while CLOS is OO, OO is not CLOS. CLOS is making implementation-specific assumptions that are not, and cannot be, part of the definition of OOP.
Take Rust as an example. Rust cannot implement runtime type changing due to its extreme performance-optimized view of objects. And it is still very much OO.
@@zoran-horvat so at the end of the day, you agree with me. The problem you state is not OO specific but more of a limitation of MOST OO environments, as implemented. Such thing is demonstrated by the mere existence of an OO system on which that limitation does not exist.
Why such belligerence? I too tend to favor funcional programming over object orientation this days, just for different reasons
Sorry, but the OOP example is horrendous.
PaidInvoice needs to be constructed with the Invoice object. Essentially just wrapping the InvoiceObject to perform the "paid" functionality. Then the implementation is clean without needing to make modification to parent/child classes. You could even modify it to allow multiple payments to the Invoice without touching any other Invoice code.
Shouldn't cause any issues with ORM as well as long as you're using a dataMapper pattern instead of activeRecord. You just need to do the same thing in the dataMapper class to add the new functionality.
What you are referring to is to use object composition to represent variable state. What you have forgot to mention is that the component would also be polymorphic, and so everything said in the video moves to the payment-related component contained in the invoice.
Work properly? Write fast and clean code? That is why you can have F#
I'd say the only thing is good software.
I have witnessed many times that programmers who are incapable of writing good OO code are equally if not more incapacitated when writing functional code. What's your story?
Bad take