Main idea:
- the concept of success with an exit or an error with an error is encoded into a type.
- Values ββ(or functions) of this type can be combined into another value of this type. Values ββwith different types of success can be compiled, but they must all have the same type of failure.
- Once the 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); } } } }
source share