Ещё один Pattern Matching на C# — теперь с построением контекста

Полтора месяца назад я опубликовал статью, посвящённую реализации соспоставления с образцом на C#. В комментарии к статье gBear справедливо отметил отсутствие контекста в кейсах. В первой версии мэтчера я сознательно проигнорировал этот механизм, так как посчитал синтаксические возможности выражений в C# недостаточными для его реализации. Однако, некоторое время спустя я понял, что нужного эффекта можно достичь путём построения Expression вручную. Под катом — реализация полноценного pattern matching.Изначально при реализации сопоставления с образцом мне хотелось сделать синтаксис case-выражения похожим на следующий: s => string.IsNullOrEmpty (s) => 0 К сожалению, в C# это является синтаксически неверным: по сути s => t => s * t Представляет собой функцию в каррированой форме. Второй идеей для case выражения было использование тернарного оператора вроде следующего: s => t? a: b Которое опять-таки невозможно по причине отсутствия в C# типа Unit (для использования в ветке else). Была идея типом для выражения b сделать Expression и передавать туда следующий case, но этому препятствует требование идентичности типов для выражений a и b.В какой-то момент я свыкся с мыслью, что реализовать контекстную связанность мне не удастся и пользовался мэтчингом в том виде, в котором он есть.Однажды в процессе отладки кода, вроде следующего

… {s => s is string, s => ((string)s).Length} … я подумал, что вместо проверки типа is можно проводить преобразование as и проверять результат этого преобразования. Тут меня осенило — ведь это же и будет по сути построением контекста! Не откладывая надолго, я взялся за реализацию.Во второй версии решено было отказаться совсем от реализации Matcher перебором лямбда-функций и использовать только деревья выражений (как в ExprMatcher). Метод Add пришлось сделать типизированым:

public void Add(Expression> binder, Expression> processor) { var bindResult = Expression.Variable (typeof (TCtx), «binded»); var caseExpr = Expression.Block ( new []{bindResult}, Expression.Assign (bindResult, Expression.Invoke (binder, Parameter)), Expression.IfThen ( Expression.NotEqual (Expression.Convert (bindResult, typeof (object)), Expression.Constant (null)), Expression.Return (RetPoint, Expression.Invoke (processor, bindResult)) )); _caseExpressionsList.Add (caseExpr); } Тип TCtx является «типом контекста» для кейса. В случае, если первое выражение вернуло не null экземпляр TCtx, выполняется второе выражение, причём аргументом для него является результат сопоставления.Предыдущий синтаксис с предикатами решено было оставить, т.к. он иногда удобнее: public void Add (Expression> condition, Expression> processor) { var caseExpr = Expression.Block ( new Expression[]{ Expression.IfThen ( Expression.Invoke (condition, Parameter), Expression.Return (RetPoint, Expression.Invoke (processor, Parameter)) )}); _caseExpressionsList.Add (caseExpr); } Т.к. выражения для кейсов теперь строятся непостредственно при добавлении, код сборки полного выражения мэтчера существенно упростился: private Func CompileMatcher () { var finalExpressions = new Expression[] { Expression.Throw (Expression.Constant (new MatchException («Provided value was not matched with any case»))), Expression.Label (RetPoint, Expression.Default (typeof (TOut))) };

var matcherExpression = Expression.Block (_caseExpressionsList.Concat (finalExpressions)); return Expression.Lambda>(matcherExpression, Parameter).Compile (); } Приведу небольшой пример функции, возвращающей значение аргумента, в случае если его тип — string или StringBuilder и строку «Unknown object» — в ином случае:

var match = new Matcher { {s => s as string, s => s}, {sb => sb as StringBuilder, sb => sb.ToString ()}, {o => true, (bool _) => «Unknown object»} }.ToFunc (); В дальнейших планах — добавить в пакет набор готовых дискриминаторов для часто встречающихся кейсов.На этом, пожалуй, всё. На всякий случай приведу ссылки на проект и nuget-пакет мэтчера: Проект на bitbucketNuget пакет

Как и в прошлый раз, буду рад замечаниям/предложениям в комментариях.Спасибо за внимание!

© Habrahabr.ru