Good review, but I frankly don't see the point of Result . Just have the error be an exception type as exceptions idiomatically represents errors in .NET. Then you're down to only 1 type argument which is much less noisy. That's what I've used for the result type in my library that I've been using for years. I don't use it often, but it's very handy when appropriate.
If you ask yourself what the meaning of the word 'exception' is and then consider how many failures are exceptional , then one quickly realises that exceptions are the worst thing you could use to represent expected failure conditions. The only time we should throw (or even pass around) exceptions is if there isn't a slot in the co-domain to inject a value in to.
Monads in C# (Part 2): Result
61–70 of 75 posts
Re: Monads in C# (Part 2): Result
#62Good review, but I frankly don't see the point of Result . Just have the error be an exception type as exceptions idiomatically represents errors in .NET. Then you're down to only 1 type argument which is much less noisy. That's what I've used for the result type in my library that I've been using for years. I don't use it often, but it's very handy when appropriate.
If you ask yourself what the meaning of the word 'exception' is and then consider how many failures are exceptional , then one quickly realises that exceptions are the worst thing you could use to represent expected failure conditions. The only time we should throw (or even pass around) exceptions is if there isn't a slot in the co-domain to inject a value in to.
[0] https://louthy.github.io/language-ext/LanguageExt.Core/Monad...
Re: Monads in C# (Part 2): Result
#63Earlier quoted context omitted.
If you ask yourself what the meaning of the word 'exception' is and then consider how many failures are exceptional , then one quickly realises that exceptions are the worst thing you could use to represent expected failure conditions. The only time we should throw (or even pass around) exceptions is if there isn't a slot in the co-domain to inject a value in to.
I think OP means using Result where Right in the Either is always implied to be Exception, much like Fin in language-ext [0] [0] https://louthy.github.io/language-ext/LanguageExt.Core/Monad...
I understood what they meant, Exception is still a poor type for declarative error handling, because it’s unclear whether an event is truly exceptional.
Re: Monads in C# (Part 2): Result
#64Earlier quoted context omitted.
If you ask yourself what the meaning of the word 'exception' is and then consider how many failures are exceptional , then one quickly realises that exceptions are the worst thing you could use to represent expected failure conditions. The only time we should throw (or even pass around) exceptions is if there isn't a slot in the co-domain to inject a value in to.
The dangers and pitfalls of exceptions are completely irrelevant if all you're doing is using an exception as a value and not for control flow.
Re: Monads in C# (Part 2): Result
#65Earlier quoted context omitted.
Doing it like this (or preferably with a custom exception) translates the technical problem into a domain problem. Without doing this, callers can't properly handle it. FormatException or OverflowException could be thrown at multiple locations, not just in parsing the user ID. This here is an InvalidUserIdException. It could be derived from ArgumentException, but IMHO InvalidOperationException is not appropriate.
You're right that domain specific exceptions would be much better. As an aside, generating domain specific exceptions is precisely the kind of busywork that traditionally it is hard to find motivation to do but that LLMs excel at.
public class UserIdInvalidException(Exception innerException) : Exception("Invalid User ID", innerException);
Even easier than most data objects you’d have to define anyway. And then, Exceptions are part of the contract. I’d rather not have an LLM write that up, but that’s just personal preference.Re: Monads in C# (Part 2): Result
#66Idk, to me that constant Result looks extremely ugly and unergonomic, even just to type. I understand that this is complicated topic and there were a lot of strong opinions even inside of Google about it, but god, I miss absl::StatusOr and ASSIGN_OR_RETURN. Yes, it won’t work without preprocessor magic (and that’s why this article goes through heavy functional stuff, otherwise it just cannot work in language like C#)…
Is this similar? https://github.com/amantinband/error-or What's some of the "preprocessor magic" that makes this[1] more ergonomic to use? [1]: https://github.com/abseil/abseil-cpp/blob/master/absl/status...
In google/c++ you can do much simpler, but with preprocessor magic.
Example:
absl::StatusOr loadUserById(int userId) { ... }
absl::Status populateItems(User user, std::vector& items) {...}
absl::StatusOr findItems(int userId) {
ASSIGN_OR_RETURN(auto user, loadUserById(userId));
std::vector items;
RETURN_IF_ERROR(populateItems(user, items));
for (auto& item: items) {
...
}
}
ASSIGN_OR_RETURN and RETURN_IF_ERROR essentially preprocessor macroses, that expand, more or less into. absl::StatusOr findItems(int userId) {
auto userOrStatus = loadUserById(userId);
if (!userOrStatus.ok()) return userOrStatus.status();
auto user = *userOrStatus;
std::vector items;
absl::Status st2 = populateItems(user, items));
if (!st2.ok()) return st2;
}
No long and ugly method invocation chains, no weirdly looking code - everything just works. You can see real life example here: https://github.com/protocolbuffers/protobuf/blob/bd7fe97e8c1...Again, even inside Google there were docs that considered those macroses bad and suggested to write straightforward code, but I'm in the camp who considers them useful and, maybe, sole good use of preprocessor macros that I ever seen, as there are no other way to clearly and concisely express that in majority of languages.
F# has something like that with Computational Expressions, but they are still limited.
Re: Monads in C# (Part 2): Result
#67Idk, to me that constant Result looks extremely ugly and unergonomic, even just to type. I understand that this is complicated topic and there were a lot of strong opinions even inside of Google about it, but god, I miss absl::StatusOr and ASSIGN_OR_RETURN. Yes, it won’t work without preprocessor magic (and that’s why this article goes through heavy functional stuff, otherwise it just cannot work in language like C#)…
> I miss absl::StatusOr Sounds like you would rather have an `ErrorOr ` than a `Result `. Both are union types wrapped in a monadic construct.
My point is not the types/monadic constructs, etc (I love to do functional jerk off as a guy next to me, though), but that there are ways to keep code readable and straightforward without neither invocation chains
DoOne().OnError().ThenDoTwo().ThenDoThree().OnError()
nor coloring/await mess, nor golang-style useless error handling noise
Re: Monads in C# (Part 2): Result
#68I really dislike this pattern: try { id = int.Parse(inputId); } catch (Exception ex) when (ex is FormatException or OverflowException) { throw new InvalidOperationException("DeactivateUser failed at: parse id", ex); } Where all you're doing when you catch an exception is throwing it in a more generic way. You could just let the FormatException or OverflowException bubble up, so the parent can handle those differently…
You're leaking implementation details if you let exceptions bubble. Sometimes this is ok if all of the callers are aware of the implementation details anyway, but it can make refactoring or changing implementations more difficult otherwise.
Re: Monads in C# (Part 2): Result
#69Earlier quoted context omitted.
You're leaking implementation details if you let exceptions bubble. Sometimes this is ok if all of the callers are aware of the implementation details anyway, but it can make refactoring or changing implementations more difficult otherwise.
You should leak implementation details on exceptions -- if an operation fails because of a network timeout or file access issue, that's useful information. Most exceptions cannot be meaningfully caught anyway so let me log with a good stack trace and be done with it.
Re: Monads in C# (Part 2): Result
#70Earlier quoted context omitted.
The dangers and pitfalls of exceptions are completely irrelevant if all you're doing is using an exception as a value and not for control flow.
It’s not about danger it’s about being declarative. That’s kinda the point of using these ‘result’ types: you’re fully declaring the codomain of the function — barring exceptions — and so if your codomain is augmented with Exception then it’s pretty hard to know whether all exceptions will be returned in value form, or just exceptional exceptions! It’s fails the declarative test.