Exception or Either monad in C#

后端 未结 5 648
庸人自扰
庸人自扰 2021-01-31 20:56

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 e

5条回答
  •  盖世英雄少女心
    2021-01-31 21:24

    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 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 type:

    public class Exceptional
    {
        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 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() and SelectMany(), that correspond to the monad's Unit and Bind functions:

    public static class ExceptionalMonadExtensions
    {
        public static Exceptional ToExceptional(this T value)
        {
            return new Exceptional(value);
        }
    
        public static Exceptional ToExceptional(this Func getValue)
        {
            return new Exceptional(getValue);
        }
    
        public static Exceptional SelectMany(this Exceptional value, Func> k)
        {
            return (value.HasException)
                ? new Exceptional(value.Exception)
                : k(value.Value);
        }
    
        public static Exceptional SelectMany(this Exceptional value, Func> k, Func 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 From(T value)
        {
            return value.ToExceptional();
        }
    
        public static Exceptional Execute(Func getValue)
        {
            return getValue.ToExceptional();
        }
    }
    
    public static class ExceptionalExtensions
    {
        public static Exceptional ThenExecute(this Exceptional value, Func 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 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(Func getValue) constructor is responsible for the exception handling and the SelectMany() method is responsible for distinguishing if a method, that was executed before, has thrown an exception.

提交回复
热议问题