2

I am curious about the utility of something like monads in the C# world.

My experience with these kinds of things is mainly through Rust but I'm a dotnet dev for work

I was thinking about the difference between Rust's result type and exception handling

https://doc.rust-lang.org/std/result/

The scenario I'm thinking through is consuming a 3rd party library as an API

In C# the library side:

void setAge(int age) {
    if (age < 0) {
        throw new IllegalArgumentException("Age cannot be negative");
    }
    this.age = age;
}

We throw an 'IllegalArgumentException'

Now when I'm calling that from my API I need to

  1. Know that it's even possible for that to be thrown which can be challenging in it's own right
  2. Catch that exception type and convert it into some BadRequest response or whatever makes the most sense

Vs if we were using a Result type

public class Result<T> {
    private final boolean success;
    private final T value;
    private final String errorMessage;

    // ... constructor and getter methods ...
}

public Result<Integer> setAge(int age) {
    if (age < 0) {
        return new Result<>(false, null, "Age cannot be negative");
    }
    this.age = age;
    return new Result<>(true, age, null);
}

Result<Integer> result = setAge(-1);
if (!result.isSuccess()) {
    // Return BadRequest
} else {
    // Return Ok
}

The Result solution seems vastly better in this specific example, less expensive since we don't have to throw anything, much more explicit in the code as both the library maintainer and the library client.

My question is, is there any argument to using Exceptions over Result in this case?

Is there an argument to be made for using something like this over Exceptions in some cases generally speaking for C#?

4
  • 1
    Just a terminology nitpick: The Result in your example is an option-type, but it is not a monad.
    – JacquesB
    Jul 11, 2023 at 17:07
  • 2
    Exceptions form a monad. pure is the identity function, bind is ordinary function composition, and there is no extra decoration of types needed
    – Caleth
    Jul 11, 2023 at 17:23
  • 5
    If I had a dollar for every time a "clever" C#/Java/etc engineer started using this kind of "result" construct, and then had to debug the failure because the next engineer didn't check isSuccess before accessing value, I'd have... well, maybe about $10 but the point is hopefully made. Jul 11, 2023 at 17:37
  • 2
    Why the tag 'monad' and mention of monads in the title when there is no monad in the question?
    – md2perpe
    Jul 14, 2023 at 20:50

3 Answers 3

3

The obvious benefit of exceptions is you don't have to check for isSuccess and manually bubble up Result<type> types throughout your whole codebase.

If you are doing functional programming you want your function to always work, exceptions break that unless you always catch them in every function, having an option type makes that handling neater.

But in normal programming exceptions mean something went wrong, you don't have to handle them at every level and bubbling up to the top is super useful.

For example, lets expand look at your calling code

Result<DateTime> GetDoB() {
    Result<Integer> result = setAge(-1);
    if (!result.isSuccess()) {
        // Return BadRequest
        return return new Result<>(false, null, result.errorMessage);

    } else {
        // Return Ok
        var dob = DateTime.Now.AddYears(-1 * result.value)
        return return new Result<>(true, dob, null);
    }
}

Main()
{
    var dob = getDoB();
    if(dob.isSuccess) {
       Console.WriteLine(dob.value);
    }
    else
    {
       Console.WriteLine(dob.ErrorMessage);
    }
}

vs

Result<DateTime> GetDoB() {
    var age = setAge(-1);
    var dob = DateTime.Now.AddYears(-1 * age)
    return dob;
}

Main()
{
    try {
       var dob = getDoB();
       Console.WriteLine(dob.value);
    }
    catch(ex)
    {
       Console.WriteLine(ex.ErrorMessage);
    }
}

you save an if block on every function call and from inside every function

2

The main advantage that exceptions have over result types as you show here is that you can implicitly 'bubble-up' an exception so that it is handled somewhere other than where you call the function/method. This is extremely useful for situations where it doesn't make sense for the caller to attempt to do something. A typical example is when dealing with IO. It's painful to have to account for potential failures everywhere you might have IO involved and often there's not enough context to do so properly.

For a situation where you never or rarely need that kind of thing i.e.: where you would always catch the exception at the call site, I think most people who work in languages like C# would say it's better not to use an exception and this approach is a completely valid alternative.

2

The biggest drawback to using option-types for errors in C# is that C# already has exceptions. Maybe you don't throw them in your own code, but the standard library and third-party libraries will throw exceptions to signal error conditions. So in practice you will need to handle both option-types and exceptions, which is surely more convoluted than just one of them.

A second drawback is that there is no syntax sugar for having an error "bubble" up the call stack by default. Rust have the '?' operator which was introduced when it was discovered how common this pattern was, but C# have nothing similar, so you will have lots of boilerplate check-and-return.

I do think option-types may be appropriate in cases they don't represent error conditions but just a special-case result. For example the Try-pattern in .net (TryParse() etc.), where a method returns a boolean and an output parameter if the result is true. This could be implemented more elegantly with an option-type.

2
  • 1
    In Java, there's an infamous integer parsing method that either returns a int or throws an exception if the string doesn't represent a valid number. IMO, something like Option would be a much preferrable approach but I'm not sure that aligns with your last sentence. Do you agree with the idea an Option would be better for that?
    – JimmyJames
    Jul 11, 2023 at 17:27
  • @JimmyJames: Yes, I would agree this would be an appropriate use of an option.
    – JacquesB
    Jul 11, 2023 at 17:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.