F#如何在递归可区分联合中指定类型限制

Posted

技术标签:

【中文标题】F#如何在递归可区分联合中指定类型限制【英文标题】:F# how to specify type restriction in recursive discriminated unions 【发布时间】:2017-05-12 06:19:57 【问题描述】:

我试图将我的语法定义为有区别的联合。它有两种可能的类型:intdatetime 以及AddMul 的数学运算符。 Add 适用于 intdatetime(在 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】:

Asti 建议的设计也是我的首选。根据您的要求,这可能就是您所需要的。

不过,它也可以让你编译像

这样的表达式
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

【讨论】:

这处理了一些限制,但不是全部 - 以下不是一个合理的表达式,但它会进行类型检查:Add(Date(DateTime.Now), Mul(Integer 1, Integer 2))

以上是关于F#如何在递归可区分联合中指定类型限制的主要内容,如果未能解决你的问题,请参考以下文章

如何在Kotlin中指定递归泛型参数?

差异联合类型和区分联合打字稿/ F#

为啥 F# 可区分联合无法使其 TypeConverter 受到 JSON.NET 的尊重,而其他类型却可以?

如何在 docker compose 版本 3 中指定内存和 CPU 限制

在 C 中指定枚举类型的大小

如何在类型提示中指定函数类型?