问题描述:

I read about monadic errors as being an alternative to return value errors and exceptions. Can someone explain and give an example of how monadic errors would work in an imperative pseudo-language (no functional examples please).

网友答案:

The basic idea:

  • the notion of success with output or fail with error is encoded into a type.
  • Values (or functions) of this type can be composed into another value of this type. Values with different success types can be composed, but they must all have the same fail type.
  • Once an error is output, the rest of the execution is short-circuited to propagate the error.

Here is a working example in C#:


// A discrimated union that can be either an error of type TError, 
// or a successful result of type TResult. 
// IsError indicates which field has a valid value.
class Throws<TError, TResult>
{
    public bool IsError;
    public TError Error;
    public TResult Result;

    // Make a new successful reslt of type TResult.
    public static Throws<TError, TResult> Success(TResult result)
    {
        Throws<TError, TResult> t = new Throws<TError, TResult>();
        t.IsError = false;
        t.Result = result;
        return t;
    }

    // Make a new error of type TError.
    public static Throws<TError, TResult> Fail(TError error)
    {
        Throws<TError, TResult> t = new Throws<TError, TResult>();
        t.IsError = true;
        t.Error = error;
        return t;
    }

    // Composition.
    public Throws<TError, TResultB> Bind<TResultB>(
              Func<TResult, Throws<TError, TResultB>> f)
    {
        if (IsError)
        {
            // If this is an error, then we can short circuit the evaluation
            return Throws<TError, TResultB>.Fail(Error);
        }

        // Otherwise, forward this result to the next computation.
        return f(Result);
    }
}

class Test
{
    // num / demom
    private static Throws<string, double> Div(double num, double denom)
    {
        if (denom == 0)
            return Throws<string, double>.Fail("divide by zero");

        return Throws<string, double>.Success(num / denom);
    }

    // Have the user enter a double.
    private static Throws<string, double> ReadDouble(string name)
    {
        Console.Write("{0}: ", name);

        string input = Console.ReadLine();
        double result;
        if (!double.TryParse(input, out result))
            return Throws<string, double>.Fail(string.Format("can't parse {0}", name));

        return Throws<string, double>.Success(result);
    }

    // Read two doubles and divide them to produce the result.
    private static Throws<string, double> Interact()
    {
        return ReadDouble("numerator").Bind(num => 
               ReadDouble("denominator").Bind(denom => 
               Div(num, denom)));
    }

    public static void TestLoop()
    {
        while (true)
        {
            // Run a computation that asks the user for two numbers,
            // divides them and then prints out the result.
            Throws<string, double> t = Interact();

            // Notice how the return type forces you to address the
            // error if you want to get to the value.
            if (t.IsError)
            {
                Console.WriteLine("Error: {0}", t.Error);
            }
            else
            {
                Console.WriteLine("Success: {0}", t.Result);
            }
        }
    }
}
相关阅读:
Top