Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a .NET type like a (value OR error) discriminated union?

In C#, I would like to have the option to handle errors in a more “functional” way, rather than always using the default exception-throwing model. In some scenarios, the throwing model is great, because it allows you to force your code to stop executing if something unexpected happens. However, the throwing model has several major drawbacks:

  1. Exception throwing is basically a whole secondary system of application control flow that subverts the main control flow system. When a method is called, its body is executed and then effectively control returns to the caller, regardless of whether it was due to a return or throw statement. If control returned to the caller from a throw statement, control will immediately move to its caller if there is no appropriate try/catch in scope; whereas if control returned from a return statement, control continues as normal. I know the implementation is way more complicated and subtle than that, but I think that’s an appropriate conceptual summary. This double system can be generally confusing, both at design time and runtime in different ways.
  2. The throwing system gets more awkward in parallel or asynchronous scenarios, as seen in the treatments of errors by System.Threading.Tasks.Task. Any exceptions thrown by a Task executing are stored in an AggregateException and accessed through the Task.Exception property by the calling code. So while the Tasks execution may be aborted, the calling code must look for errors stored in object properties, using normal C# control flow.
  3. Aside from XML comments, there is no metadata about whether a method might throw exceptions, or which it might throw. Exceptions behave as an alternate form of method output, but are largely ignored by the type system. For example, the method int Divide(int x, int y) could result in either an integer or a DivideByZeroException, but the signature of this method does not suggest anything regarding errors. Conversely, I’ve often heard complaints about Java’s checked exceptions, where every specific exception type a method can throw must be added to its signature, which can get very wordy. To me the simplest middle ground would be a generic type like Nullable<T> that contains either a value or an exception. A method like Divide would then have this signature: Fallible<int> Divide(int x, int y). Any operations using the result would then need to deal with the error case. Methods could also take Fallible parameters to allow easier chaining.

Here is an implementation of Fallible I sketched out:

public class Fallible<T> : IEquatable<Fallible<T>> {

    #region Constructors

    public Fallible() {
        //value defaults to default(T)
        //exception defaults to null
    }

    public Fallible(T value) : this() {
        this.value = value;
    }

    public Fallible(Exception error) : this() {
        if (error == null) throw new ArgumentNullException(nameof(error));
        Error = error;
    }

    public Fallible(Func<T> getValue) : this() {
        if (error == null) throw new ArgumentNullException(nameof(getValue));
        try {
            this.value = getValue();
        }
        catch(Exception x) {
            Error = x;
        }
    }

    #endregion


    #region Properties

    public T Value {
        get {
            if (!HasValue) throw new InvalidOperationException("Cannot get Value if HasValue is false.");
            return value;
        }
    }
    private T value;

    public Exception Error { get; }

    public bool HasValue => Error == null;

    #endregion


    #region Equality

    public bool Equals(Fallible<T> other) => (other != null)
        && Equals(Error, other.Error)
        && Equals(Value, other.Value);

    public override bool Equals(object obj) => Equals(obj as Fallible<T>);

    public static bool operator ==(Fallible<T> a, Fallible<T> b) {
        if (a == null) return b == null;
        return a.Equals(b);
    }

    public static bool operator !=(Fallible<T> a, Fallible<T> b) {
        if (a == null) return b != null;
        return !a.Equals(b);
    }

    public override int GetHashCode() =>
        HasValue 
            ? Value.GetHashCode() 
            : Error.GetHashCode();

    #endregion


    public override string ToString() =>
        HasValue
            ? $"Fallible{{{Value}}}"
            : $"Fallible{{{Error.GetType()}: {Error.Message}}}";
}

And the questions:

  1. Is there already something like Fallible<T> for .NET? Perhaps a class in the Task Parallel Library, Reactive Extensions, or the F# core libraries?
  2. Are there any major deficiencies in the implementation above?
  3. Are there any conceptual problems with control flow that I may be overlooking?
like image 504
JamesFaix Avatar asked Nov 25 '16 20:11

JamesFaix


People also ask

What is a discriminated union C#?

Discriminated Unions are a functional programming convenience that indicates that something is one of several different types of objects. For example, a User might be an unauthenticated user, a regular user, or an administrator.

How many types of error are there in C#?

In Visual Basic, errors fall into one of three categories: syntax errors, run-time errors, and logic errors.

Does C# have union types?

Union types are common in functional languages, but have yet to make it to C# and similar. These types solve a simple and very common problem, data that is one of a finite set of possibilities.


2 Answers

There isn't anything like this built into the BCL, though FSharp.Core does include support for options, which provide another mechanism by which you can avoid exception handling.

The Language Ext project does contain a Try<T> type which is very similar to what you're describing. In addition, it has a C# friendly Option<T> with pseudo pattern matching for operations on the options.

like image 118
Reed Copsey Avatar answered Nov 10 '22 00:11

Reed Copsey


The https://github.com/mcintyre321/OneOf project is a C# library intended to imitate F# discriminated unions in a generic way. You could use that to implement your own Result type as a OneOf<ResultData, ErrorData> (where you've defined the ResultData and ErrorData classes with whatever specific things your data model needs).

like image 45
rmunn Avatar answered Nov 10 '22 00:11

rmunn