Exception or Either monad in C#

14,188

Solution 1

While learning a bit about monads in C#, for exercise I implemented an Exceptional monad for myself. With this monad, you can chain up operations that might throw an Exception like in these 2 examples:

var exc1 = from x in 0.ToExceptional()
           from y in Exceptional.Execute(() => 6 / x)
           from z in 7.ToExceptional()
           select x + y + z;
Console.WriteLine("Exceptional Result 1: " + exc1);

var exc2 = Exceptional.From(0)
           .ThenExecute(x => x + 6 / x)
           .ThenExecute(y => y + 7);
Console.WriteLine("Exceptional Result 2: " + exc2);

Both expressions yield the same result, just the syntax is different. The result will be an Exceptional<T> with the arisen DivideByZeroException set as property. The first example shows the "core" of the monad using LINQ, the second contains a different and perhaps more readable syntax, which illustrates the method chaining in a more understandable way.

So, how it's implemented? Here's the Exceptional<T> type:

public class Exceptional<T>
{
    public bool HasException { get; private set; }
    public Exception Exception { get; private set; }
    public T Value { get; private set; }

    public Exceptional(T value)
    {
        HasException = false;
        Value = value;
    }

    public Exceptional(Exception exception)
    {
        HasException = true;
        Exception = exception;
    }

    public Exceptional(Func<T> getValue)
    {
        try
        {
            Value = getValue();
            HasException = false;
        }
        catch (Exception exc)
        {
            Exception = exc;
            HasException = true;
        }
    }

    public override string ToString()
    {
        return (this.HasException ? Exception.GetType().Name : ((Value != null) ? Value.ToString() : "null"));
    }
}

The monad is completed through extension methods ToExceptional<T>() and SelectMany<T, U>(), that correspond to the monad's Unit and Bind functions:

public static class ExceptionalMonadExtensions
{
    public static Exceptional<T> ToExceptional<T>(this T value)
    {
        return new Exceptional<T>(value);
    }

    public static Exceptional<T> ToExceptional<T>(this Func<T> getValue)
    {
        return new Exceptional<T>(getValue);
    }

    public static Exceptional<U> SelectMany<T, U>(this Exceptional<T> value, Func<T, Exceptional<U>> k)
    {
        return (value.HasException)
            ? new Exceptional<U>(value.Exception)
            : k(value.Value);
    }

    public static Exceptional<V> SelectMany<T, U, V>(this Exceptional<T> value, Func<T, Exceptional<U>> k, Func<T, U, V> m)
    {
        return value.SelectMany(t => k(t).SelectMany(u => m(t, u).ToExceptional()));
    }
}

And some little helper structures, that are not part of the monad's core:

public static class Exceptional
{
    public static Exceptional<T> From<T>(T value)
    {
        return value.ToExceptional();
    }

    public static Exceptional<T> Execute<T>(Func<T> getValue)
    {
        return getValue.ToExceptional();
    }
}

public static class ExceptionalExtensions
{
    public static Exceptional<U> ThenExecute<T, U>(this Exceptional<T> value, Func<T, U> getValue)
    {
        return value.SelectMany(x => Exceptional.Execute(() => getValue(x)));
    }
}

Some explanation: a method chain built with this monad is executed as long as one method of the chain throws an exception. In this case no more method of the chain will be executed and the first thrown exception will be returned as part of an Exceptional<T> result. In this case the HasException and Exception properties will be set. If no Exception occurs, HasException will be false and the Value property will be set, containing the result of the executed method chain.

Note that the Exceptional<T>(Func<T> getValue) constructor is responsible for the exception handling and the SelectMany<T,U>() method is responsible for distinguishing if a method, that was executed before, has thrown an exception.

Solution 2

We have implemented Either data structure in our C# solution and we are happy using it. Here is the most simple version of such implementation:

public class Either<TL, TR>
{
    private readonly TL left;
    private readonly TR right;
    private readonly bool isLeft;

    public Either(TL left)
    {
        this.left = left;
        this.isLeft = true;
    }

    public Either(TR right)
    {
        this.right = right;
        this.isLeft = false;
    }

    public T Match<T>(Func<TL, T> leftFunc, Func<TR, T> rightFunc)
        => this.isLeft ? leftFunc(this.left) : rightFunc(this.right);

    public static implicit operator Either<TL, TR>(TL left) => new Either<TL, TR>(left);

    public static implicit operator Either<TL, TR>(TR right) => new Either<TL, TR>(right);
}

(our code has more helper methods, but they are optional)

The main points are

  • You are only able to set Left or Right
  • There are implicit operators to make instantiation easier
  • There is a Match method for pattern matching

I've also described how we use this Either type for data validation.

Solution 3

It's worth noting that there are C# libraries available now that contain implementations of Either:

language-ext library is available for .Net 4.5.1 and .Net Standard 1.3

LaYumba library is available for .Net Standard 1.6 and .Net Core 1.1.

Both libraries are well documented, with LaYumba being used as the basis of the Manning book Functional Programming in C#.

Solution 4

So - don't know if anyone is interested - I've come up with a very preliminary implementation very much following Mike Hadlow's lead. Some of it doesn't feel quite right atm but it is a start. (Having said that, I wouldn't use it - you might lose a million dollars or even kill someone - just my caveat!)

A very simple sample of the kind of code that could be written is

var exp = from a in 12.Div(2)
          from b in a.Div(2)
          select a + b;

Assert.AreEqual(9, exp.Value());

var exp = from a in 12.Div(0)
          from b in a.Div(2)
          select b;

Assert.IsTrue(exp.IsException());

with the Div method implemented as follows:

public static IExceptional<int> Div(this int numerator, int denominator)
{
    return denominator == 0
           ? new DivideByZeroException().ToExceptional<int, DivideByZeroException>()
           : (numerator / denominator).ToExceptional();
}

or

public static IExceptional<int> Div_Throw(this int numerator, int denominator)
{
    try
    {
        return (numerator / denominator).ToExceptional();
    }
    catch (DivideByZeroException e)
    {
        return e.ToExceptional<int, DivideByZeroException>();
    }            
 }

(Straight away I can see an potential improvement to the api but am unsure quite how to achieve it. I think that this

new DivideByZeroException().ToExceptional<int, DivideByZeroException>()

would be nicer if it were

new DivideByZeroException().ToExceptional<int>()

You'll see my implementation later and hopefully someone will be able to re-architect it for the above.)

The monadic bit is done in here (mainly)

public static class Exceptional
{
    public static IExceptional<TValue> ToExceptional<TValue>(this TValue result)
    {
        return new Value<TValue>(result);
    }

    public static IExceptional<TValue> ToExceptional<TValue,TException>(this TException exception) where TException : System.Exception
    {
        return new Exception<TValue, TException>(exception);
    }


    public static IExceptional<TResultOut> Bind<TResultIn, TResultOut>(this IExceptional<TResultIn> first, Func<TResultIn, IExceptional<TResultOut>> func)
    {                
        return first.IsException() ? 
                ((IInternalException)first).Copy<TResultOut>() : 
                func(first.Value());
    }


    public static IExceptional<TResultOut> SelectMany<TResultIn, TResultBind, TResultOut>(this IExceptional<TResultIn> first, Func<TResultIn, IExceptional<TResultBind>> func, Func<TResultIn, TResultBind, TResultOut> select)
    {
        return first.Bind(aval => func(aval)
                    .Bind(bval => select(aval, bval)
                    .ToExceptional()));
    }   
}

The main interface is specified as

public interface IExceptional<TValue>
{
    bool IsException();    
    TValue Value();
}

and I have an internal interface I use to get at the exception which has been thrown (more later)

internal interface IInternalException 
{
    IExceptional<TValue> Copy<TValue>();     
}

The concrete implementations are as follows:

public class Value<TValue> : IExceptional<TValue>
{
    TValue _value = default(TValue);

    public Value(TValue value)
    {
        _value = value;
    }

    bool IExceptional<TValue>.IsException()
    {
        return false;
    }

    TValue IExceptional<TValue>.Value()
    {
        return _value;
    }
}

public class Exception<TValue, TException> : IInternalException, IExceptional<TValue> where TException : System.Exception
{
    TException _exception = default(TException);

    public Exception(TException exception)
    {
        _exception = exception;
    }

    bool IExceptional<TValue>.IsException()
    {
        return true;
    }

    IExceptional<TOutValue> IInternalException.Copy<TOutValue>()
    {
        return _exception.ToExceptional<TOutValue,TException>();
    }

    TException GetException()
    {
        return _exception;
    }

    TValue IExceptional<TValue>.Value()
    {
        return default(TValue);
    }
}

Just a word of explanation ... the trickiest bit, for me, was the Bind operation when an exception has arisen. If you are dealing with a pipeline of operations and an exception gets thrown early on in the process, you need to perpetuate that exception down the pipeline so that when the expression completes the returning IExceptional contains the exception which occurred earlier. This is the reason for the IInternalException. It enables me to create a new IExceptional of the same or (potentially different) type (eg IExceptional --> IExceptional) but copies across the exception to the new IExceptional without me having to know the type of the internal exception.

No doubt there are loads of improvements possible. eg I could see that you may potentially want to keep track of the error stack within the IExceptional. There is probably redundant code or better ways to achieve the ends ... but ... it was meant to be a bit of learning for me.

Any thoughts/suggestions would be gratefully received.

Share:
14,188

Related videos on Youtube

Simon Woods
Author by

Simon Woods

Updated on September 16, 2022

Comments

  • Simon Woods
    Simon Woods over 1 year

    I am trying to grok get a preliminary understanding of monads.

    I have a data layer call whose result I would like to return monadically either as a result eg no of rows updated/dataset etc, or an exception. I figure I need to use the Exception monad which I could see as a special case of the Either monad

    I've looked round at various samples - tonnes of Maybe samples, and I am not quite sure how or if to generalise this to become an Either monad - but I can't find any which are not in haskell - and, unfortunately, I most certainly don't grok haskell!

    I was wondering if anyone could point me to any samples.

    • Enigmativity
      Enigmativity over 11 years
      @flq - The Exception monad is useful if you are processing many items (perhaps a LINQ query) and you want all of the computations to complete despite one or more failing with an exception.
    • flq
      flq over 11 years
      @Enigmativity that sounds useful indeed, I'll have to give that a spin.
    • flq
      flq
      Personally I find it quite difficult to learn a concept without seeings its benefits. I have used an implementation of Maybe and some monadic operations on those with the benefit of dealing with null references. i am unsure what the benefit of the Exceptional monad could be if you consider there are language-specific mechanisms for dealing with exceptions that make a lot of sense in an imperative language
  • Simon Woods
    Simon Woods almost 12 years
    Thx, but it is a bit of a learning exercise for me so was hoping to study someones implementation to try and understand the concepts.
  • Euphoric
    Euphoric almost 12 years
    If you want to learn, then you should learn in enviroment, where the learning makes sense. I never heard about monads making sense in C#. If you want to learn monads, then learn Haskel.
  • stmax
    stmax almost 12 years
    They would make sense in C#, we just haven't grokked them yet.
  • Simon Woods
    Simon Woods almost 12 years
    as a matter of interest, have you used it at all in the "real world"?
  • Matthias
    Matthias almost 12 years
    Nope, not yet. But it depends on my project environment. I fear only a few get this stuff right in their heads ;-)
  • Simon Woods
    Simon Woods almost 12 years
    Have you any idea of the sort of scenario/use case where you may choose this over traditional try... catch in c#? S
  • Matthias
    Matthias almost 12 years
    I'm not sure... As defined above, it doesn't seem to give you more value. But if you extend Exceptional with some more helper methods, it could be great. Imagine an extension method "OnException", it would be a great replacement for a catch block. This could come in handy if you normally would have a chain of try/catch blocks that arrange consecutively. Monads only make sense if you can chain things together (the Maybe monad is the best example here).
  • Enigmativity
    Enigmativity over 11 years
    What kind of support do you think C# needs to support monads? And why do you say that the LINQ support isn't meant for general monads? The C# designers went out of their way to make LINQ work with duck-typing to make it as flexible as possible for any type. The use of extension methods also make monads possible.
  • StriplingWarrior
    StriplingWarrior about 6 years
    Thanks for pointing out language-ext. My searches for C# Monads always turned up louthy's other C# Monad library which I found to be opaque and unusable, but language-ext looks great.
  • Sean Kearon
    Sean Kearon about 6 years
    Glad that helped! :)
  • superjos
    superjos about 5 years
    Just to copy here a note found on LaYumba project readme: Note: you are welcome to reference LaYumba.Functional from your projects via NuGet, and submit PRs with improvements, but the main intent of this library is pedagogical. For a more fully-fledged functional library, consider LanguageExt