About me

Michael L Perry

Improving Enterprises

Principal Consultant

@michaellperry

User login

Toward Pattern Matching in C#

A powerful tool in many functional programming languages is called "pattern matching". It is a concise syntax for expressing conditions based on the shape of a value. For example, in Haskell, the factorial function can be expressed as a pattern:

factorial :: (Integral a) => a -> a  
factorial 0 = 1  
factorial n = n * factorial (n - 1)

Learn You a Haskell: Syntax in Functions

Often the pattern is expressed in terms of the type of the value. For example, Maybe is a type that can either be Nothing or Just some value.

applyMaybe :: Maybe a -> (a -> Maybe b) -> Maybe b  
applyMaybe Nothing f  = Nothing  
applyMaybe (Just x) f = f x

Learn You a Haskell: A Fistful of Monads

Notice that the pattern extracts the parts of the value, and gives you names for those parts.

Toward patterns in C#

So what would patterns look like in C#? Here's a start. I'm not sure where I will take this, if anywhere.

Say we wanted to return one of two things from a function, based on whether it was successful or not.

private Pattern<int, Error> ParseInteger(string s, string description)
{
    int value;
    if (!int.TryParse(s, out value))
        return new Error(new List<string>
        {
            String.Format("Invalid value for {0}: {1}.", description, s)
        });
    else
        return value;
}

We define a Pattern class taking generic parameters indicating the types that could be returned. To make it possible to return the types directly as written above, we define implicit operators.

public abstract class Pattern<TOption1, TOption2>
{
    public static implicit operator Pattern<TOption1, TOption2>(TOption1 value)
    {
        return new Match<TOption1, TOption1, TOption2>(value);
    }

    public static implicit operator Pattern<TOption1, TOption2>(TOption2 value)
    {
        return new Match<TOption2, TOption1, TOption2>(value);
    }
}

Here we create a Match type, which inherits Pattern to represent one of the two options.

class Match<TValue, TOption1, TOption2> : Pattern<TOption1, TOption2>
{
    private readonly TValue _value;

    internal Match(TValue value)
    {
        _value = value;
    }
}

Unfortunately, there is no way in C# to express the constraint that TValue is either TOption1 or TOption2. However, that constraint is implicit in the way that a Match is constructed. By making this class private and its constructor internal, we ensure that only classes in the assembly (i.e. Pattern) can create a Match.

Selecting a path based on the pattern

Now that we have the Pattern class, and can construct a Match to satisfy the pattern, we can select our response. For example, we could do the following:

ParseInteger(s, "iteration count")
    .If<int>(i => _configuration.IterationCount = i)
    .If<Error>(HandleError)

Here HandleError is a method taking an Error object. We can define the If method in Pattern as follows:

public abstract class Pattern<TOption1, TOption2>
{
    public abstract Pattern<TOption1, TOption2> If<T>(Action<T> action);
}

Again, we can't express the generic constraint that T is one of TOption1 or TOption2. And this time, we don't have any context that enforces that relationship. We just need to trust that the consumer of this API wouldn't write something that would never match.
We satisfy this abstract method in the Match derived class.

class Match<TValue, TOption1, TOption2> : Pattern<TOption1, TOption2>
{
    public override Pattern<TOption1, TOption2> If<T>(Action<T> action)
    {
        if (typeof(T) == typeof(TValue))
        {
            action((T)(object)_value);
            return Matched;
        }
        return this;
    }
}

The C# compiler could actually expand the generic parameters at compile time and come up with a constant for the condition. That would allow it to optimize this method body to one of the two branches. I don't think it actually does, but it could be measured.

The Matched object returned when the condition is matched is a simple flyweight that always fails an If.

class Matched<TOption1, TOption2> : Pattern<TOption1, TOption2>
{
    public override Pattern<TOption1, TOption2> If<T>(Action<T> action)
    {
        return this;
    }
}

The flyweight is instantiated in Pattern.

public abstract class Pattern<TOption1, TOption2>
{
    protected static readonly Pattern<TOption1, TOption2> Matched = new Matched<TOption1, TOption2>();
}

Future directions

To make this Pattern class generally useful, it needs to be expanded in a number of ways. First, the number of options must be made flexible. It should support three, four, or possibly even just one option. This could be done through nesting (Pattern<T1, Pattern<T2, T3>>).

Second, the If method should also support conditions. In Haskell, these are called guard clauses. It isn't just the type of the expression that matters, it's also the value.

Third, there should be an Else method that is called if none of the If methods match. This should be easy to construct.

Fourth, the If method should take Func<T, TResult>, and not just Action<T>. You should be able to use pattern matching within an expression. Quite possibly, the expression would return yet more Pattern types based on the types of all of its results, so that patterns could be further composed.

And fifth, we should be able to concisely express an action to be taken at any point within a composition. For example, the HandleError method used above should be called on any path where Error is encountered.

There are almost certainly more ways that this concept can be expanded. Do you have any ideas you would like to try?