Pattern matching in C# – part 2

Last time, I just stopped before generating any real expression tree… Now is the perfect time to continue the process !

In order to handle match expressions where none of the expressions would match a given candidate, I decided to use an option type. This allows the generation of this type of expressions:

Func<double, Option<double>> matchSomeOrNone =
    (double d) =>
        (d > 0.0)
        ? Option<double>.Some(d)
        : Option<double>.None();

Given this these options, the translation of last time’s sample code:

var stringPattern = PatternMatcher
    .CreateSwitch<string, double>()
    .Case("TEST"                        , s => Math.PI)
    .Case("TEST_1"                      , s => Math.E)
    .Case((string)null                  , s => 0.0)
    .Case(s => s.StartsWith("TEST__")   , s => Math.E * 2)
    .CaseElse(                            s => s.Length);

would become:

Func<string, Option<double>> generatedFuncOption =
    (string s) =>
        (s == "TEST")
            ? Option<double>.Some(Math.PI)
            : (s == "TEST_1")
                ? Option<double>.Some(Math.E)
                : (s == null)
                    ? Option<double>.Some(0.0)
                    : s.StartsWith("TEST__")
                        ? Option<double>.Some(Math.E * 2)
                        : Option<double>.Some(s.Length);

In order to instantiate option types in LINQ expressions, I have to use the corresponding MethodInfo instances, obtained from reflexion :

MethodInfo createSomeMethodInfo =
    typeof(Option<TResult>)
        .GetMethod(
            "Some",
            BindingFlags.Public | BindingFlags.Static);

MethodInfo createNoneMethodInfo =
    typeof(Option<TResult>)
        .GetMethod(
            "None",
            BindingFlags.Public | BindingFlags.Static);

And then, in order to finally build a matching expression, I loop over the match conditions and result selector expressions the following way:

private Func<TSource, Option<TResult>> BuildPatternMatchingFunc()
{
    ParameterExpression parameter =
        Expression.Parameter(typeof(TSource));

    Expression noneExpression = Expression.Call(
        createNoneMethodInfo);

    Expression caseExpression = noneExpression;

    foreach (var matchCase in this.cases.Reverse())
    {
        caseExpression =
            Expression.Condition(
                Expression.Invoke(
                    matchCase.TestExpression,
                    parameter),
                Expression.Call(
                    createSomeMethodInfo,
                    Expression.Invoke(
                        matchCase.SelectorExpression,
                        parameter)),
                caseExpression);
    }

    return Expression
        .Lambda<Func<TSource, Option<TResult>>>(
            caseExpression,
            parameter)
        .Compile();
}

The createNoneMethodInfo and createSomeMethodInfo are the MethodInfo corresponding to the generic Option<T>.None() and Option<T>.Some() methods obtained by reflection.

Debugging this kind of expression building can be complicated, particularly without the ability to visualize clearly the expression tree… Hopefully, I have built an expression tree visualizer in the past (I could also have used the easy path and install the classical one) ! From the C# sample again, I can produce the following expression tree:

This tree may seem wide, but is is just a succession of conditional expressions… Finally, the the function that evaluates the result of the pattern-matching expression is the following one:

public TResult Eval(TSource candidate)
{
    if (this.patternMatchingFunc == null)
    {
        this.patternMatchingFunc = BuildPatternMatchingFunc();
    }

    Option<TResult> matchResult = patternMatchingFunc
                                    .Invoke(candidate);

    if (!matchResult.IsSome)
        throw new NoMatchFoundException();

    return matchResult.Value;
}

As you can see, it’s a wrapper around the expression evaluation invocation. It first generates the expression  instantiates upon the first call (in a non-threadsafe way, but who cares !) and handles “nothing matches” case by throwing an exception. And this works !

Now, you might say that matching a single value in of very limited interest. I can’t deny it. Why don’t we try to match several values, then ?

This entry was posted in Functional Inspiration, Syntax Puzzles and tagged , , , . Bookmark the permalink.

Comments are closed.