❔ Reducing Result-pattern annoyances
I know Result patterns can be a bit controversial as far as their benefits, but I'm giving them a try in my project.
Anyways...I've outsourced some work to a scoped service which returns a Result<T>. My methods which call this outsourced code have this exact same structure:
Are there any ways to reduce this into a simpler form to further reduce the amount of code needed to perform the "try action, return error message if error else get success value" process? I certainly will concede I think I'm splitting hairs here and this is already simple enough for some, but I feel like I'm...missing something to make this more streamlined - maybe pattern matching could be utilized here?
5 Replies
Hey Kiel, good question. Preferably you should provide the code that you have in the
Result<T
type that you have, but just from the info provided in your message, I might have some suggestions.
You can define some commonly-used operations as higher order functions on the original result type.
Don't be scared of the term higher order function, it's just a fancy way of saying a function that either accepts or returns a callback. (think System.Func
s, System.Action
s of .NET).
I'm assuming you have something like this in your code, alongside some ctors or static factory methods to initalize instances of Result
You can use something like the "TryParse" pattern if you want
Let's start with some basic operations that you can try defining on this.
Let's call the first one
Map
or you may know it better as Select
(from Linq).
It's purpose is to replace the value that can potentially be wrapped in the result or do nothing if the result is failed.
This is more like pseudocode rather than something that will actually compile, but try to understand the concept.
And a usage of it would be similar to this.
Note that Transform will be called only if the first step was successful, otherwise the original error message would be propagated to the final result.
This is a very simple case, in which only the first 'step' of the algorithm you're executing may fail.
You can think of some other common cases and extract their logic to methods on the base Result type.
For example.
What if the second step may also fail and you don't want to endlessly wrap Results inside Results?
What if you want the algorithm to continue even if one of the steps failed while aggregating all the errors in the process?
What if some steps of the algorithm might need to execute asynchronously?
What if you're not mapping the value wrapped in the result but instead doing something with it (like saving to a database?)
What if you have a list of results and want to turn it into a Result of a list?
Think about what's practical for you and what's not and try to extract the common logic to the Result class, it's not as anemic as it seems at first.Hey there, thanks for the thoughtful responses and sorry for not getting back sooner.
ATM, my result class is as such:
ATM, the use case will likely not exceed the original design and usage that I proposed in the original message - I found this just a lot easier to use than Exception-based flow where I'd either return a value or throw an Exception - try/catch everywhere is ugly and also very repetitive
it sucks I am limited by what C# is capable of as a language, as many SO results have pointed out, this is something functional languages are great at, and I have to just deal with the hand I'm dealt.
In my perfect world, it would be cool to have a
return
statement on the right-hand side of a null coalescing operator like how it's possible with throw
:
this would cut the boilerplate code needed to ensure obj
was obtained successfully in half, which is a cool improvement. I wonder if there's a proposal out there...Was this issue resolved? If so, run
/close
- otherwise I will mark this as stale and this post will be archived until there is new activity.