I like it. Reminds me of writing Rx.js on our Angular front end. And I appreciate you making videos about these kinds of new/unorthodox approaches because if nothing else it stretches out the ol' brain a bit and that's good for the community.
14:20 absolutly agree about worse readability, but not because of chaining, but because not realized potential. Can be improved significantly by providing more explicit naming and maybe more extensions methods and by realization of async potential by making all calls async and using Cancelation token.
@@MilanJovanovicTech This works: Result Function(Result arg1, Result arg2) => (arg1.Value, arg2.Value); var tuple = Function(Result.Success(5), Result.Success(6)); Console.WriteLine(tuple.Value.Arg1);
I'm in love with this Result implementation. it's so easy and has a possibility to be extended without refactoring, great! As I started learning Rust on the background, I totally understood that results with errors is a great feature and idea itself
Yes, you are correct. I'm definitely going to say that this is harder to read and the cost is paid in terms of educating the new team members with more time spent on bind and tap type extensions. Less lines of code? Not less enough for the extra load on cognitive ability imposed.
The cost of educating the team is probably a ~30-45 min. intro on the basics, and everyone can start being productive. All this fuss about not readable and more cognitive load is coming from people that never even tried FP or ROP, so how can the argument be valid?
@@MilanJovanovicTech In real world, the least complex solution wins. We need to understand the cost and benefits trade off. Also, kindly don't assume that everyone gets on board with such custom frameworks. It's not just training, but the real win is in adoption. It's definitely not a capability question.
@@nayanchoudhary4353 Really not that hard dude. Maybe/Option and Result are literally part of several functional programming languages.If you're written Typescript you know all about map and tap.
Personally, I like using a combination of imperative and functional programming, as the language offers us. For example, when some functional chain gets more complex, I prefer breaking it into partial results with suggestive variable names. It offers a better experience at both debugging and reading the code.
This definitely had a learning curve but so does every style. I can see how powerful this style of coding can be. I do not believe reading this would be more difficult than reading a Linq statement
Carrying values forward is a real pain, in your example it was handy that you were able to extend the entity to have the gathering and member. But it doesn't always make sense to do this.
Hi Milan! I'm a fan of your videos and I think we follow the same people in the programming world because I agree with you in many of the patterns you present in your videos. I also use "Result", "Bind", "Match", etc,... in the application layer and in the domain layer. But I would like to point out that I only use "Result" to indicate an error in a user input instead of throwing exceptions. To me, exceptions should be just that "Exceptions". That is, although I like to use "Result" objects for specific circumstances, just as exceptions are only used for real exception cases, to return an object from the repository, I do not use "Result", since here we are not validating a user input. A repository (provider or wahtever) returns an object or not, but it is not a "Result". In this case I use the "Option" or "Maybe" paradigm, also from functional programming. Also with a "Maybe" object we can still use "Railway Programming" and combine it with Results. Also, I have seen first hand in my teams that abuse of Result can lead to wrong implementations of the pattern which can lead to painful refactorings. Therefore, I use it very cautiously and only where it really applies, in my opinion of course. thanks for your videos!
How is Maybe different from a Result when returning from a repository? - Maybe will have a `HasValue` flag - Result will have a `IsSuccess` flag The only thing different is the intent - so I only partially agree with your point
@@MilanJovanovicTech Well, I believe that intention is precisely the key. Use each thing precisely for what it was designed to avoid misunderstandings. The Maybe implementation I use, has no hasValue flag or similar, a Maybe can be Some or None, simply that. At the same time we avoid that the client is forced to check if the returned object is null or not (it is possible that the client forgets to do that check, we all know the errors that have occurred in the history of nulllable objects....😅). In my opinion, the code is more declarative and easy to reason about, just what functional programming promotes.
@@Tamer_Ali Not any time soon, need to finish other projects. But at some point I'll circle back to ROP and do a "from scratch" video to try to explain the benefits better. People are so confused and can't see the value in it.
Hi Milan, I like your video as I myself use this functional approach on my current project. All is custom code, no external library. I started with the Result class and ended up having many extensions. I tried to use as few namings as possible, to avoid confusion like: Match, OnSuccess, OnFailure. This is all. I saw libraries that use the extensions you defined Tap, Bind, but I really don't understand why do we have to come up with different namings for something that should be simpler. Tap and Bind could be called OnSuccess instead. Am I missing something? Also, I'd love to see a video with the differences between the Result and Maybe/Option types. More exactly when to use one over the other. In my opinion if you could return an error then use a Result, otherwise an Option. For instance what if you're trying to get an inexistent user from the db? Is that a Result or an Option? You could expect not to find the user, but we could also encounter another issues (connection). Probably a result is still the right choice. There is no clear boundary between them and everyone online has different opinions. Looking forward for yours! Thx!
You can choose the naming convention you like. OnSuccess is to generic for me. I prefer the RXJS convention, which uses `tap`, for example. As for returning Maybe/Option - I've found that nullable reference types work just as fine for this use case.
Impressive, but very condensed. I would only use .Map() and .Bind() for a Result type. Not because the other methods don't work, it just ends up being more unreadable (in C#). Let semicolon and whitespace do their thing.
I really like the Result abstraction, and the operators helps getting rid of a lot of unwrapping. One problem I often counter, is accessing some of the previous results in the chain (here solved by adding a property to the invitation). With linq support you can solve this - something like: await ( from gathering in Result.Create(await _gatheringRepository.GetByIdWithCreatorAsync(request.GatheringId, cancellationToken)) from member in Result.Create(await _memberRepository.GetByIdAsync(request.MemberId, cancellationToken)) from invitation in gathering.SendInvitation(member) from _ in Result.Create(async () => { _invitationRepository.Add(invitation); await _unitOfWork.SaveChangesAsync(cancellationToken) _emailService.SendInvitationSentEmailAsync( member, gathering, cancellationToken ) }) select Unit.Value ) To me this is more readable, but it often confuses OOP programmers (which I completely understands!). What are you thoughts on this?
Quite a bit verbose, but I agree that it's definitely a problem when you want to reuse a previous value. I'll try to research a little if there's a better way to achieve this.
I don't think it is more verbose than using callbacks - I mean from x in result ... vs result.Map(x => ...) I would use the same approach for ienumerable and rx. The mental translation between linq and result operators can be a bit tricky though - but I guess that is some of hard parts when introducing Monads in C# (and in your team). You could probably use Where for side effect and some boolean operators (+ or &). I feel it could become a great abstraction, but it might be too big a change for many programmers. Another thing - what about stronly typed errors? If you repositories returns Result, and SendInvitation returns Result - how would you handle that? I would like to ensure the caller are forced to handle these and only these (not say MemberAlreadyExists). This is where I usually lay down on the floor, cry a bit and fall back to throwing exceptions...
Unfortunately, this looks and feels rather clunky in an OO-first language 😕 This works so much better with computation expressions in F#. Same problem I have with Sum types. There's the OneOf library to try to get them in C#, but it's extremely clunky to work with compared to FP languages. Even though trying to hack functional concepts in C# seens like a good idea in theory, it becomes very hard to read (and thus, maintain) in C# in practice.
I have are question. This function public static Result Success(TValue value) => new Result(true, Error.None); return value operations ?. Forecsample public Result Bar1() { int res = 2+2; return Result.Success(res); }
@@MilanJovanovicTech at 4.14 minutes you were showing the Result class. it has a function public static Result Success(TValue value) => new(value,true, Error.none); can this function return a result? Forecsample public Result Bar1() { int res = 2+2; return Result.Success(res); }
@@MilanJovanovicTech I dabbled a bit, I can definitely agree that doing it a lot makes it easier to understand, that's part of the reason I don't find it very readable. But even after doing some FP, the biggest problem I have with it is I find it hard to tell where a method call starts and ends. There are too many levels of indentation and brackets for my taste.
@@MilanJovanovicTech lol just a tease friend. All code is "sooner or later" compiled into procedural code at the assembly and machine code levels. I've always personally felt like "rail-road" simply emulates the "true nature" of the cpu architecture.
This becomes a terrible abstraction. I have to write 3-4 time more code to get simple functionality (eg. get some data from DB in API) only to fullfill "clean code". DISASTER !!!! Are we serious going to this direction ? I'm a dotnet developer with 15 years of experience. I've written a lot of production code, only with controllers, and services. All works perfect all the time. I see here more "theory" than practical code. It's my opinion. In Polish we say: "Przerost formy nad treścią", it can be translated like: "More unusefull form, less good content" :)
Want to master Clean Architecture? Go here: bit.ly/3PupkOJ
Want to unlock Modular Monoliths? Go here: bit.ly/3SXlzSt
I like it. Reminds me of writing Rx.js on our Angular front end. And I appreciate you making videos about these kinds of new/unorthodox approaches because if nothing else it stretches out the ol' brain a bit and that's good for the community.
Exactly, quite similar to Rx.js!
14:20 absolutly agree about worse readability, but not because of chaining, but because not realized potential. Can be improved significantly by providing more explicit naming and maybe more extensions methods and by realization of async potential by making all calls async and using Cancelation token.
This is what they're called in FP, which is why only OOP people are complaining on this video 😅
Assuming this is above C# 7, you can name your tuple components so it’s more specific than Item1 and Item2.
How would you name them in a generic context?
@@MilanJovanovicTechThis should work: . Haven't tested it...
@@CodeBallast It does... But try it with a generic method
@@MilanJovanovicTech This works:
Result Function(Result arg1, Result arg2) => (arg1.Value, arg2.Value);
var tuple = Function(Result.Success(5), Result.Success(6));
Console.WriteLine(tuple.Value.Arg1);
I'm in love with this Result implementation. it's so easy and has a possibility to be extended without refactoring, great! As I started learning Rust on the background, I totally understood that results with errors is a great feature and idea itself
There you go, someone with an open mind who gets it, and sees the benefits. Thank you! 😁
Easy ?? Who are you ?? God or devil???
How are you understood?
I'm copying this clas and they didn't work...
Yes, you are correct. I'm definitely going to say that this is harder to read and the cost is paid in terms of educating the new team members with more time spent on bind and tap type extensions. Less lines of code? Not less enough for the extra load on cognitive ability imposed.
Reads pretty easily to me. You just have to know what Tap, Bind and Map are. This code is much cleaner than the original code.
The cost of educating the team is probably a ~30-45 min. intro on the basics, and everyone can start being productive. All this fuss about not readable and more cognitive load is coming from people that never even tried FP or ROP, so how can the argument be valid?
@@MilanJovanovicTech In real world, the least complex solution wins. We need to understand the cost and benefits trade off. Also, kindly don't assume that everyone gets on board with such custom frameworks. It's not just training, but the real win is in adoption. It's definitely not a capability question.
A standard iq'ed developer should understand this in 30minutes
@@nayanchoudhary4353 Really not that hard dude. Maybe/Option and Result are literally part of several functional programming languages.If you're written Typescript you know all about map and tap.
Personally, I like using a combination of imperative and functional programming, as the language offers us.
For example, when some functional chain gets more complex, I prefer breaking it into partial results with suggestive variable names. It offers a better experience at both debugging and reading the code.
Get the best of both worlds!
This definitely had a learning curve but so does every style. I can see how powerful this style of coding can be. I do not believe reading this would be more difficult than reading a Linq statement
Precisely, but most people won't even give it a chance 😅
Carrying values forward is a real pain, in your example it was handy that you were able to extend the entity to have the gathering and member. But it doesn't always make sense to do this.
Agreed, this a short-coming of FP
Hi Milan!
I'm a fan of your videos and I think we follow the same people in the programming world because I agree with you in many of the patterns you present in your videos. I also use "Result", "Bind", "Match", etc,... in the application layer and in the domain layer.
But I would like to point out that I only use "Result" to indicate an error in a user input instead of throwing exceptions. To me, exceptions should be just that "Exceptions". That is, although I like to use "Result" objects for specific circumstances, just as exceptions are only used for real exception cases, to return an object from the repository, I do not use "Result", since here we are not validating a user input. A repository (provider or wahtever) returns an object or not, but it is not a "Result". In this case I use the "Option" or "Maybe" paradigm, also from functional programming. Also with a "Maybe" object we can still use "Railway Programming" and combine it with Results.
Also, I have seen first hand in my teams that abuse of Result can lead to wrong implementations of the pattern which can lead to painful refactorings. Therefore, I use it very cautiously and only where it really applies, in my opinion of course.
thanks for your videos!
How is Maybe different from a Result when returning from a repository?
- Maybe will have a `HasValue` flag
- Result will have a `IsSuccess` flag
The only thing different is the intent - so I only partially agree with your point
@@MilanJovanovicTech Well, I believe that intention is precisely the key. Use each thing precisely for what it was designed to avoid misunderstandings. The Maybe implementation I use, has no hasValue flag or similar, a Maybe can be Some or None, simply that. At the same time we avoid that the client is forced to check if the returned object is null or not (it is possible that the client forgets to do that check, we all know the errors that have occurred in the history of nulllable objects....😅). In my opinion, the code is more declarative and easy to reason about, just what functional programming promotes.
can you make a video doing a unit test for this function?
Yes!
Great content! Thanks Milan!
My pleasure!
10:20 why don't you add Cancellation token to Func as Generic parameter?
Sure, you can do that
For what reason? Some asynchronous functions could be without cancellation token.
@@kirillhorn3186 for reason to get it where it needed instead of having troubles with rewriting all chain later.
Thanks Milan, looking for more of functional code using C#.
I have a question, How to apply transaction to this code?
Could be another call to Tap where you open a transaction
Or create a method that runs in a transaction
@@MilanJovanovicTech you are going to create another video for that?
@@Tamer_Ali Not any time soon, need to finish other projects. But at some point I'll circle back to ROP and do a "from scratch" video to try to explain the benefits better. People are so confused and can't see the value in it.
Hi Milan,
I like your video as I myself use this functional approach on my current project. All is custom code, no external library.
I started with the Result class and ended up having many extensions. I tried to use as few namings as possible, to avoid confusion like: Match, OnSuccess, OnFailure. This is all. I saw libraries that use the extensions you defined Tap, Bind, but I really don't understand why do we have to come up with different namings for something that should be simpler. Tap and Bind could be called OnSuccess instead.
Am I missing something?
Also, I'd love to see a video with the differences between the Result and Maybe/Option types. More exactly when to use one over the other.
In my opinion if you could return an error then use a Result, otherwise an Option. For instance what if you're trying to get an inexistent user from the db? Is that a Result or an Option? You could expect not to find the user, but we could also encounter another issues (connection). Probably a result is still the right choice.
There is no clear boundary between them and everyone online has different opinions. Looking forward for yours!
Thx!
You can choose the naming convention you like. OnSuccess is to generic for me. I prefer the RXJS convention, which uses `tap`, for example.
As for returning Maybe/Option - I've found that nullable reference types work just as fine for this use case.
Isn't it cause problems in MVVM and MAUI or WPF? I mean interface named ICommand?
Different namespace
Impressive, but very condensed. I would only use .Map() and .Bind() for a Result type. Not because the other methods don't work, it just ends up being more unreadable (in C#). Let semicolon and whitespace do their thing.
The intent of the other methods is the point
Is this result extensions based in some kind of library like CSharp Functional Extensions ?
Kinda
Yes, it's concise. However, it's difficult to read and learn, especially for junior developers. How is the debugging issue?
Assume I give you a 30min crash course of the basics. What's confusing with ROP style of code?
@@MilanJovanovicTech wow the sound is great. Thanks a lot.
How can we handle bulk operations fx a list of Dto coming from endpoint then we need to validate and insert in one transaction?
Might make another video to cover that
@@MilanJovanovicTech would be great
How are you doing this 😮, omg, I'm want to do this, how time much are you learning.
I spent more time than I'd like to admit tinkering with this approach 😅
CSharpFunctionalExtensions
I should check out that library
No more exceptions, yeepee 🎉
They can still happen, but you can introduce a TryCatch function to solve it
So your Result class gonna be full fledged library one day 😅
Nah, don't have such aspirations
@@MilanJovanovicTech Do you have source code which you demo in video on a github repo, if yes, can you please share link?
I really like the Result abstraction, and the operators helps getting rid of a lot of unwrapping.
One problem I often counter, is accessing some of the previous results in the chain (here solved by adding a property to the invitation). With linq support you can solve this - something like:
await (
from gathering in Result.Create(await _gatheringRepository.GetByIdWithCreatorAsync(request.GatheringId, cancellationToken))
from member in Result.Create(await _memberRepository.GetByIdAsync(request.MemberId, cancellationToken))
from invitation in gathering.SendInvitation(member)
from _ in Result.Create(async () => {
_invitationRepository.Add(invitation);
await _unitOfWork.SaveChangesAsync(cancellationToken)
_emailService.SendInvitationSentEmailAsync(
member,
gathering,
cancellationToken
)
})
select Unit.Value
)
To me this is more readable, but it often confuses OOP programmers (which I completely understands!). What are you thoughts on this?
Quite a bit verbose, but I agree that it's definitely a problem when you want to reuse a previous value. I'll try to research a little if there's a better way to achieve this.
I don't think it is more verbose than using callbacks - I mean
from x in result
...
vs
result.Map(x => ...)
I would use the same approach for ienumerable and rx. The mental translation between linq and result operators can be a bit tricky though - but I guess that is some of hard parts when introducing Monads in C# (and in your team). You could probably use Where for side effect and some boolean operators (+ or &). I feel it could become a great abstraction, but it might be too big a change for many programmers.
Another thing - what about stronly typed errors? If you repositories returns Result, and SendInvitation returns Result - how would you handle that? I would like to ensure the caller are forced to handle these and only these (not say MemberAlreadyExists). This is where I usually lay down on the floor, cry a bit and fall back to throwing exceptions...
👋👋
Unfortunately, this looks and feels rather clunky in an OO-first language 😕 This works so much better with computation expressions in F#. Same problem I have with Sum types. There's the OneOf library to try to get them in C#, but it's extremely clunky to work with compared to FP languages.
Even though trying to hack functional concepts in C# seens like a good idea in theory, it becomes very hard to read (and thus, maintain) in C# in practice.
I think the main stepping stone will be your team's willingness to embrace FP, and not so much C#'s OO nature
I have are question. This function public static Result Success(TValue value) =>
new Result(true, Error.None); return value operations ?.
Forecsample
public Result Bar1()
{
int res = 2+2;
return Result.Success(res);
}
I'm not sure what the question is?
@@MilanJovanovicTech at 4.14 minutes you were showing the Result class.
it has a function
public static Result Success(TValue value) => new(value,true, Error.none);
can this function return a result?
Forecsample
public Result Bar1()
{
int res = 2+2;
return Result.Success(res);
}
@@angelldark6426 Yes, but it should be a Result return type
Shorter, but less readable IMO.
Have you done FP before?
@@MilanJovanovicTech I dabbled a bit, I can definitely agree that doing it a lot makes it easier to understand, that's part of the reason I don't find it very readable. But even after doing some FP, the biggest problem I have with it is I find it hard to tell where a method call starts and ends. There are too many levels of indentation and brackets for my taste.
It's pronounced "procedural"
What is?
@@MilanJovanovicTech lol just a tease friend. All code is "sooner or later" compiled into procedural code at the assembly and machine code levels. I've always personally felt like "rail-road" simply emulates the "true nature" of the cpu architecture.
This becomes a terrible abstraction. I have to write 3-4 time more code to get simple functionality (eg. get some data from DB in API) only to fullfill "clean code". DISASTER !!!! Are we serious going to this direction ? I'm a dotnet developer with 15 years of experience. I've written a lot
of production code, only with controllers, and services. All works perfect all the time. I see here more "theory" than practical code. It's my opinion. In Polish we say: "Przerost formy nad treścią", it can be translated like: "More unusefull form, less good content" :)
Did you do Functional Programming in those 15 years?