Re: Abstracting Away From Exceptions

I was going to comment on this post, but ended up writing way too much so I figure I’ll post my thoughts here instead.

In the comments, Hristo Deshev suggested making the method generic rather than the class. I figure, why not make the exception generic as well?

public static class Adverse
{
  public static TResult Call<TResult, TException>(Func<TResult> attempt,
                                                  Func<TException, TResult> recover
                                                 ) where TException : Exception
  {
    try { return attempt(); }
    catch (TException e) { return recover(e); }
  }
}

Regarding Aaron Powell’s concern about not wanting to handle all exceptions the same, you can always rethrow an exception you don’t recognize:

var res = Adverse.Call(() => { return ""; },
  (ArgumentException ae) =>
  {
    if (ae is ArgumentNullException)
      return "Null";
    else if (ae is ArgumentOutOfRangeException)
      return "OOR";
    else
      throw ae;
  });

And finally, a few comments pointed out that a method that doesn’t accept arguments isn’t really a function. So I set out to add support for an argument. Here is my first attempt:

public static TResult Call<TArg, TResult, TException>(TArg arg,
                                                      Func<TArg, TResult> attempt,
                                                      Func<TArg, TException, TResult> recover
                                                     ) where TException : Exception
{
    try { return attempt(arg); }
    catch (TException e) { return recover(arg, e); }
}

It gets the job done, but I found the usage rather awkward:

var r1 = Call(5,
    i => string.Format("{0} squared is {1}", i, i * i),
    (int i, ArithmeticException e) => string.Format("Could not square {0}", i));

This got me thinking about how F# supports currying and partial function application, in other words functions that return functions with fewer arguments. Applied here, we can write a method that returns a function from TArg to TResult:

public static Func<TArg, TResult> Call<TArg, TResult, TException>(Func<TArg, TResult> attempt,
                                                                  Func<TArg, TException, TResult> recover
                                                                 ) where TException : Exception
{
    return (arg) =>
    {
        try { return attempt(arg); }
        catch (TException e) { return recover(arg, e); }
    };
}

So instead of accepting an argument in Call(), we pass the argument into the function it returns:

var snd = Call(s => string.Format("The second character of {0} is {1}", s, s[1]),
    (string s, IndexOutOfRangeException e) => string.Format("{0} has no 2nd character", s));

foreach (string s in new string[] { "ab", "c", "de" })
    Console.WriteLine(snd(s));

Or an argument can be passed directly:

var str = Call(o => o.ToString(), (object o, NullReferenceException e) => "(null)")(myObj);

This could easily be extended to support functions with multiple arguments.

I’m not sure if I’ll ever get around to using this pattern, but Vesa Karvonen provides good justification for doing so:

…a try-catch statement has to produce a result via a side-effect, because it doesn’t return any result (it returns unit/void). For example, it has to update a variable declared outside of the try-catch statement. Whether or not both the tried statement and the handler statement update the variable is not apparent at the type level.

Thoughts?

Update 1/11/2009:

Paul suggests using a params array of “recover” delegates to handle different Exception types. I looked into it at first, but without covariance the delegates couldn’t be strongly typed (can’t assign Func<string, ArgumentException, bool> to Func<string, Exception, bool>). And if they’re all of type Func<string, Exception, bool>, how do we pick which one to use?

I don’t have VS 2010, but I would think C# 4.0 could resolve this, assuming Func<T1, T2, TResult> is redefined as Func<in T1, in T2, out TResult>.

With covariance, we could do something like this:

public static Func<TArg, TResult> Call<TArg, TResult>(Func<TArg, TResult> attempt,
                                                      params Func<TArg, Exception, TResult>[] recover)
{
  return (arg) =>
  {
    try { return attempt(arg); }
    catch (Exception e)
    {
      foreach(var r in recover)
      {
        Type rType = r.GetType().GetGenericArguments()[1];
        if (rType.IsInstanceOfType(e))
          return (TResult)r.DynamicInvoke(arg, e);
      }
      throw e;
    }
  };
}

As with try…catch, more specific types would need to be listed first. It would be great if someone with access to C# 4.0 could give this a try…

Advertisements