Pattern Matching Discriminated Union в C# через кодогенерацию

6yrop

Определяем интерфейс
 

public interface IXxxMatcher<out T>
{
T Item1(string argument);
T Item2(int argument);
T Item3(int argument1, DateTime argument2);
}

После кодогенерации доступен вот такой Pattern Matching:
 

private static void Main
{
Console.WriteLine(Method1(Xxx.Item1("Test1";
Console.WriteLine(Method1(Xxx.Item2(1;
Console.WriteLine(Method1(Xxx.Item3(1, DateTime.Now;
}

private static string Method1(IXxx xxx)
{
return xxx.Match(
item1: argument => argument,
item2: argument => argument.ToString
item3: (argument1, argument2) => "" + argument1 + argument2);
}

Кодогенерация генерирует вот такую простыню. Фишка в том, что всё уже определено в интерфейсе IXxxMatcher.
 

public interface IXxx
{
T Match<T>(IXxxMatcher<T> matcher);
}

public static class Xxx
{
public static IXxx Item1(string argument)
{
return new Item1Impl(argument);
}

private class Item1Impl : IXxx
{
private readonly string argument;

public Item1Impl(string argument)
{
this.argument = argument;
}

public T Match<T>(IXxxMatcher<T> matcher)
{
return matcher.Item1(argument);
}
}

public static IXxx Item2(int argument)
{
return new Item2Impl(argument);
}

private class Item2Impl : IXxx
{
private readonly int argument;

public Item2Impl(int argument)
{
this.argument = argument;
}

public T Match<T>(IXxxMatcher<T> matcher)
{
return matcher.Item2(argument);
}
}

public static IXxx Item3(int argument1, DateTime argument2)
{
return new Item3Impl(argument1, argument2);
}

private class Item3Impl : IXxx
{
private readonly int argument1;
private readonly DateTime argument2;

public Item3Impl(int argument1, DateTime argument2)
{
this.argument1 = argument1;
this.argument2 = argument2;
}

public T Match<T>(IXxxMatcher<T> matcher)
{
return matcher.Item3(argument1, argument2);
}
}

public static T Match<T>(
this IXxx it,
Func<string, T> item1,
Func<int, T> item2,
Func<int, DateTime, T> item3)
{
return it.Match(new XxxMatcher<T>(
item1,
item2,
item3;
}
}

public class XxxMatcher<T> : IXxxMatcher<T>
{
private readonly Func<string, T> item1;
private readonly Func<int, T> item2;
private readonly Func<int, DateTime, T> item3;

public XxxMatcher(
Func<string, T> item1,
Func<int, T> item2,
Func<int, DateTime, T> item3)
{
this.item1 = item1;
this.item2 = item2;
this.item3 = item3;
}

public T Item1(string argument)
{
return item1(argument);
}

public T Item2(int argument)
{
return item2(argument);
}

public T Item3(int argument1, DateTime argument2)
{
return item3(argument1, argument2);
}
}

P.S. в компиляторе C# в framework 4.0 есть небольшой баг, который пофиксили, я проверил на framework 4.5

Dasar

а реальное использование в какой задаче?

karkar

А проверка на exhaustiveness есть? Если нет, легко ли добавить?

agaaaa

Выглядит как переизобретение discriminated union'ов.
В F# они примерно так и реализованы.

agaaaa

А проверка на exhaustiveness есть? Если нет, легко ли добавить?
Судя по коду, там нужно все кейсы обработать, в функции Match аргументы же не опциональные.
Чтобы наоборот кейсы игнорировать можно ввести функции Ignore генерик типов Func<T, Result>, Func<T1, T2, Result> и т.д.

6yrop

А проверка на exhaustiveness есть? Если нет, легко ли добавить?
имеется ввиду надо ли писать паттерн для каждого item-а? да, это контролируется компилятором.

6yrop

Выглядит как переизобретение discriminated union'ов.
ну, да, этого и хотелось, только без смены языка

6yrop

а реальное использование в какой задаче?
о том для чего мне это понадобилось напишу позже (сейчас на работу опаздываю :grin: ). А сейчас отмечу, что в первом варианте IXxxMatcher назывался IXxxVisitor :p .
Если взять пример, который уже был на нашем форуме
то вот этот код будет сгенерирован автоматически:

abstract class BinaryNode : INode
{
protected readonly INode left;

protected readonly INode right;

protected BinaryNode(INode left, INode right)
{
this.left = left;
this.right = right;
}

public abstract T ProcessBy<T>(INodeVisitor<T> visitor);
}

class Add : BinaryNode
{
public Add(INode left, INode right) : base(left, right)
{
}

public override T ProcessBy<T>(INodeVisitor<T> visitor)
{
return visitor.OnAdd(left, right);
}
}

class Sub : BinaryNode
{
public Sub(INode left, INode right) : base(left, right)
{
}

public override T ProcessBy<T>(INodeVisitor<T> visitor)
{
return visitor.OnSub(left, right);
}
}

class Mul : BinaryNode
{
public Mul(INode left, INode right) : base(left, right)
{
}

public override T ProcessBy<T>(INodeVisitor<T> visitor)
{
return visitor.OnMul(left, right);
}
}

class Constant : INode
{
readonly double val;

public Constant(double val)
{
this.val = val;
}

public T ProcessBy<T>(INodeVisitor<T> visitor)
{
return visitor.OnConstant(val);
}
}

class VariableX : INode
{
public T ProcessBy<T>(INodeVisitor<T> visitor)
{
return visitor.OnVariableX;
}
}

только без BinaryNode, а он и не нужен.

karkar

А аналог _ есть? Типа
case a of
| Item1 x => blabla
| _ => otherwise

6yrop

Аналога "_" нет. А оно нужно? Это же по сути противоречит exhaustiveness, мы добавили Item4 и нам компилятор не показывает все места, где идет ветвление, а прелесть exhaustiveness в том, что мы явно должны прописывать все случае, причем даже те, которые появляются в будущем.

apl13

А оно нужно?
Главное просто этого не хотеть.

6yrop

Главное просто этого не хотеть.
Речь идет не о желании или нежелании, есть обоснование почему это плохо, см. пост выше.

apl13

а прелесть exhaustiveness в том, что мы явно должны прописывать все случае, причем даже те, которые появляются в будущем.
Хорошо-хорошо. Напиши мне, пожалуйста, исчерпывающее определение функции, определяющей, является ли целое число нулем. :4u:

6yrop

целые числа определяются как Discriminated Union?

apl13

data Int = ... | -2 | -1 | 0 | 1 | 2 | ...

:D
Мы ведь не обсуждаем, что в шарпе опять можно придумать кривой костыль для какой-то давно всеми используемой фичи, который пригоден только для семидесяти пяти сотых случая, правда?

6yrop


bool IsZero(int x)
{
return x == 0;
}

apl13

Какая прелесть! :applause:

6yrop

мы же не обсуждаем, что в каких-то языках можно теоретические изъебы выписывать, правда? Не хочешь приводить примера из реальной практики, тогда лучше промолчи.

apl13

Не хочешь приводить примера из реальной практики, тогда лучше промолчи.
Ебтать.
Расскажи мне, пожалуйста, какой тебе нужен реальный пример, чтобы ты не превратил его в цепочку ифов.
Можно поподробнее. :umnik2:

6yrop

давай так. Ты чего хочешь показать/доказать?

apl13

Ну например, что
имеется ввиду надо ли писать паттерн для каждого item-а? да, это контролируется компилятором.
— несколько странное требование.

6yrop

несколько странное требование.
Можно это переформулировать в более конструктивные термины? Сложно вести конструктивный разговор в терминах "не хотеть", "странное". Не инженерные это термины.

apl13

Можно это переформулировать в более конструктивные термины?
Можно, только я подозреваю, что ты все равно не поймешь.

6yrop

Можно, только я подозреваю, что ты все равно не поймешь.
неумение объяснить часто свидетельствует о том, что ты сам не понимаешь

6yrop

а реальное использование в какой задаче?
Приvер 1. Реализация Workflow:
 

public interface IStateMatcher<out T>
{
T State1;
T State2;
T State3;
}

public interface ITransitionMatcher<out T>
{
T State1ToState2(IState fromState, IState toState);
T State1ToState3(IState fromState, IState toState);
T State2ToState3(IState fromState, IState toState);
}

public static IEnumerable<ITransition> GetTransitions(this IState state)
{
return state.Match(
state1: => new []
{
Transition.State1ToState2(state, State.State2
Transition.State1ToState3(state, State.State3
},
state2: => new []
{
Transition.State2ToState3(state, State.State3
},
state3: => new ITransition[] { }
);
}

Пример 2. По вот такому
1. выгружать объекты в XML (Matcher1);
2. генерировать XSD схему (Matcher2);
3. загружать объекты из XML (Matcher3).

6yrop

можно и вот так сделать

public interface ITransitionMatcher<out T>
{
T Transition(State1 fromState, State2 toState);
T Transition(State1 fromState, State3 toState);
T Transition(State2 fromState, State3 toState);
}

6yrop

Пример 3. Реализация Option Type

6yrop

Мы ведь не обсуждаем, что в шарпе опять можно придумать кривой костыль для какой-то давно всеми используемой фичи, который пригоден только для семидесяти пяти сотых случая, правда?
оказывается всё ни так просто ;) . В случае C# и кодогенерации, вот такой поддерживается без проблем, а в твоем фичастом языке без костылей как такое реализуется? Надеюсь, ты способен не только играться в песочнице с целыми числами и продемонстрируешь свои аргументы на реальном примере.
Изюминка тут в том, что требуется разрешить overloading членов discriminated union по аргументам. Если такое разрешено в языке, то тогда как такие члены буду указываться вне match-а?
В случае кодогенерации поступаем просто, если overloading есть, то члены скрыты в виде приватных классов, если overloading-а нет, то классы публичны.

Dmitriy82

Я правильно понимаю, что этот подход не позволяет использовать составные шаблоны? Если так, то весь смысл паттерн матчинга теряется.

6yrop

Я правильно понимаю, что этот подход не позволяет использовать составные шаблоны? Если так, то весь смысл паттерн матчинга теряется.
там у пор на связку pattern matching-а с discriminated union
Оставить комментарий
Имя или ник:
Комментарий: