F # как указать ограничение типа в рекурсивных дискриминационных объединениях

Я пытаюсь определить свою грамматику как дискриминационный союз. Он имеет два возможных типа: int и datetime и математические операторы Add и Mul. Add работает на int и datetime (как add days in int) Mul работает только на int, а не на datetime Грамматика может быть рекурсивной

Моя грамматика выглядит как

type MyExpression =
|Integer of int
|Date of datetime
|Add of MyExpression * MyExpression
|Mul of MyExpression * MyExpression

Я написал парсер (fparsec), который может анализировать текст в моей грамматике, но я не уверен, как обрабатывать условие, что Mul может быть рекурсивным, но только на Integer.

Есть ли возможность определить это ограничение для моего типа MyExpression или мне нужно обработать это в моем анализируемом вводе?

Ответ 1

Дизайн, предложенный Асти, также станет моим первым выбором. В зависимости от ваших требований, это может быть все, что вам нужно.

Однако он также позволяет вам скомпилировать выражение типа

Add(Val(System.Console.Out), Val(System.Console.Error))

который, вероятно, не является тем, что вы хотите.

В качестве альтернативы вы можете моделировать выражения следующим образом:

open System

type IntExpression =
| Integer of int
| Mul of IntExpression * IntExpression
| Add of IntExpression * IntExpression

type DateTimeExpression =
| Date of DateTime
| Add of DateTimeExpression * DateTimeExpression

type MyExpression =
| IntExpression of IntExpression
| DateTimeExpression of DateTimeExpression

Это явно более подробное определение типа, но оно воплощает правило, согласно которому выражение может содержать листовые узлы из целых чисел или значений DateTime, а другие значения - если это правило вы хотите применить.

Я не утверждаю, что это лучше; Я только предлагаю альтернативу.

Использование:

> IntExpression(Mul(IntExpression.Add(Integer(1), Integer(2)),Integer 3));;
val it : MyExpression =
  IntExpression (Mul (Add (Integer 1,Integer 2),Integer 3))
> DateTimeExpression(Add(Date(DateTime.MinValue),Date(DateTime.MinValue)));;
val it : MyExpression =
  DateTimeExpression
    (Add
       (Date 01.01.0001 00:00:00 {Date = 01.01.0001 00:00:00;
                                  Day = 1;
                                  DayOfWeek = Monday;
                                  DayOfYear = 1;
                                  Hour = 0;
                                  Kind = Unspecified;
                                  Millisecond = 0;
                                  Minute = 0;
                                  Month = 1;
                                  Second = 0;
                                  Ticks = 0L;
                                  TimeOfDay = 00:00:00;
                                  Year = 1;},
        Date 01.01.0001 00:00:00 {Date = 01.01.0001 00:00:00;
                                  Day = 1;
                                  DayOfWeek = Monday;
                                  DayOfYear = 1;
                                  Hour = 0;
                                  Kind = Unspecified;
                                  Millisecond = 0;
                                  Minute = 0;
                                  Month = 1;
                                  Second = 0;
                                  Ticks = 0L;
                                  TimeOfDay = 00:00:00;
                                  Year = 1;}))

Ответ 2

Если у вас есть ограничения по типу, может быть проще использовать общий подход:

type MyExpression<'t> =
|Val of 't
|Mul of MyExpression<int> * MyExpression<int>
|Add of MyExpression<'t> * MyExpression<'t>

let Integer (x:int) = Val(x)
let Date (x:DateTime) = Val(x)

Использование:

Mul(Integer(1), Integer(2)) //compiles
Mul(Date(DateTime.Now), Date(DateTime.Now)) //error