What you're describing is a Monad. In particular the Try
monad. I'll show below how to implement a Try monad, and your code for CalculateSomething
will look like this:
public Try<int> CalculateSomething(int input) =>
from x in DoSomethingA(input)
from y in DoSomethingB(x)
select y;
Which by any measure is pretty easy to understand.
First declare a delegate that will represent the Try operation:
public delegate TryResult<T> Try<T>();
Next define TryResult<T>
. It will capture the return value on-success, and the failure exception on-fail:
public struct TryResult<T>
{
internal readonly T Value;
internal readonly Exception Exception;
public TryResult(T value)
{
Value = value;
Exception = null;
}
public TryResult(Exception e)
{
Exception = e;
Value = default(T);
}
public static implicit operator TryResult<T>(T value) =>
new TryResult<T>(value);
internal bool IsFaulted => Exception != null;
public override string ToString() =>
IsFaulted
? Exception.ToString()
: Value.ToString();
public static readonly TryResult<T> Bottom = new InvalidOperationException();
}
Next define some extension methods for the Try
delegate (this is a little known feature of C# that delegates support extension methods):
public static class TryExtensions
{
public static TryResult<T> Try<T>(this Try<T> self)
{
try
{
if (self == null) return TryResult<T>.Bottom;
return self();
}
catch (Exception e)
{
return new TryResult<T>(e);
}
}
public static R Match<T, R>(this Try<T> self, Func<T, R> Succ, Func<Exception, R> Fail)
{
var res = self.Try();
return res.IsFaulted
? Fail(res.Exception)
: Succ(res.Value);
}
}
They both allow invoking of the Try
delegate in a safe way. The key is the Match
extension method, which 'pattern matches' on the result:
int res = CalculateSomething(1).Match(
Succ: value => value,
Fail: excep => 0
);
So you're forced to acknowledge that the function could throw an exception to get at the value.
One thing that's missing here is how it works with LINQ:
public static class TryExtensions
{
public static Try<U> Select<T, U>(this Try<T> self, Func<T, U> select) =>
new Try<U>(() =>
{
var resT = self.Try();
if (resT.IsFaulted) return new TryResult<U>(resT.Exception);
return select(resT.Value);
});
public static Try<V> SelectMany<T, U, V>(
this Try<T> self,
Func<T, Try<U>> bind,
Func<T, U, V> project ) =>
new Try<V>(() =>
{
var resT = self.Try();
if (resT.IsFaulted) return new TryResult<V>(resT.Exception);
var resU = bind(resT.Value).Try();
if (resU.IsFaulted) return new TryResult<V>(resT.Exception);
return new TryResult<V>(project(resT.Value, resU.Value));
});
}
Select
allows this to work:
var res = from x in y
select x;
SelectMany
allows this to work:
var res = from x in y
from z in x
select z;
That is it allows multiple from statements to run in sequence. This is known as monadic bind (but you don't need to know that for this to work - an I'd rather not write a monad tutorial here). Essentially it is capturing the nesting pattern in your CalculateSomething
example, so you never have to manually write it again.
And that's it. The code above, you write once and once only. Now let's implement your DoSomething
functions:
public Try<int> DoSomethingA(int inputInt) => () =>
inputInt;
public Try<int> DoSomethingB(int inputInt) => () =>
{
throw new Exception();
};
Note how they're defined as lambdas. If you're using C# 5 or lower they would look like this:
public Try<int> DoSomethingA(int inputInt)
{
return () => inputInt;
}
If you want to see a fuller implementation of this, check out my language-ext functional library for C# where there's a Try implementation there. It has many more useful extension methods that allow you to write functional code without the bolierplate of try/catch.