Another Pattern Matching in C #

Recently, in the process of working with the C # language, I became more and more acute in need of a pattern matching mechanism, which is present in many modern multi-paradigm languages ​​(F #, Scala, etc.), but is not available in C #. The implementations found thanks to half an hour of googling ( Example ) suggested constructing match expressions using fluent interfaces, which, in my opinion, is rather cumbersome syntax. The second drawback, which is already more essential for real use, is overhead to search for predicates in the loop, which occurs “under the hood” in such meters. In general, I set out to write my own implementation of the comparison with the sample, guided by two basic principles:
  • Syntactically closer to “normal” constructs as in F # / Scala
  • Get as close as possible to code with if / else if / else as much as possible


For what happened - I ask for a cut


Appearance


Initially, I wanted to make the design of the matcher look like a “real” one, avoiding the chaining method calls. It was decided to use a list of pairs "predicate - function". In order to use the shorthand list initialization syntax, the Matcher class implements IEnumerable and also has an Add method. For ease of use (for example, for passing to Select), an implicit cast to Func method was added to the class <>

Here's what it looks like when using:
Func match = new Matcher
{
    {s => string.IsNullOrEmpty(s), s => 0},
    {s => true, s => s.Length}
};
int len1 = match(null); // 0
int len2 = match("abc"); // 3


Implementation


The first implementation, written in the search for syntax, was “naive” and, like the ones found, performed predicates in turn with the parameter passed to Match. When the code began to satisfy on the first point (being outwardly not bulky), I rewrote the matcher using Expression <>:

public class ExprMatcher : IEnumerable>, Expression>>>
    {
        private Func _matcher;
        private Func Matcher
        {
            get { return _matcher ?? (_matcher = CompileMatcher()); }
        } 
        private readonly List>, Expression>>> _caseList = new List>, Expression>>>();
        public void Add(Expression> predicate, Expression> function)
        {
            _caseList.Add(new Pair>, Expression>>(predicate, function));
        }
        private Func CompileMatcher()
        {
            var reverted = Enumerable.Reverse(_caseList).ToList();
            var arg = Expression.Parameter(typeof(TIn));
            var retVal = Expression.Label(typeof(TOut));
            var matcher = Expression.Block(
                        Expression.Throw(Expression.Constant(new MatchException("Provided value was not matched with any case"))),
                        Expression.Label(retVal, Expression.Constant(default(TOut)))
                    );
            foreach (var pair in reverted)
            {
                retVal = Expression.Label(typeof(TOut));
                var condition = Expression.Invoke(pair.First, arg);
                var action = Expression.Return(retVal, Expression.Invoke(pair.Second, arg));
                matcher = Expression.Block(
                    Expression.IfThenElse(condition, action, Expression.Return(retVal, matcher)),
                    Expression.Label(retVal, Expression.Constant(default(TOut)))
                    );
            }
            return Expression.Lambda>(matcher, arg).Compile();
        }
        public TOut Match(TIn value)
        {
            return Matcher(value);
        }
        public static implicit operator Func(ExprMatcher matcher)
        {
            return matcher.Match;
        }
        public IEnumerator>, Expression>>> GetEnumerator()
        {
            return _caseList.GetEnumerator();
        }
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }


When calling the Match () method or casting to Func, a chained expression is thrown that throws a MatchException if the argument does not satisfy any of the predicates. As a result, we get only an overhead in the form of Expression compilation time.

Algebraic types


Another disadvantage of using C # for me was the lack of union types in it. I wanted to add them, but at the same time make their use as safe (for NPE) as possible.
For starters, a combination of two types was implemented:
public sealed class Union
{
	public object Value { get; private set; }
	public T1 Value1 { get; private set; }
	public T2 Value2 { get; private set; }
	public Union(T1 value)
        {
                Value1 = value;
	        Value = value;
        }
	public Union(T2 value)
        {
                Value2 = value;
		Value = value;
        }
	public static explicit operator T1(Union value)
        {
                return value.Value1;
        }
	public static explicit operator T2(Union value)
        {
                return value.Value2;
        }
        public static implicit operator Union(T1 value)
        {
                return new Union(value);
        }
        public static implicit operator Union(T2 value)
        {
               return new Union(value);
        }
}

Depending on the parameter passed to the constructor, either Value1 or Value2 is initialized in the instance, and Value is also initialized. This allows you to compare checking the type of Value in the predicate using is, without worrying that the value will take on any type other than T1 and T2. Using the t4 template engine, Union overloads of up to 17 types were generated.
In order to simplify the initialization of the matchers, the heirs of Matcher and ExprMatcher were written:
public class ExprMatcher : ExprMatcher> {}


To complete the picture, a rather trivial Option was also written.

I hope that my matcher will be useful to someone:
Project on bitbucket
Nuget package

Thank you for your attention!

Also popular now: