在继续讨论复杂示例之前,我试图弄清 F# 的基础知识。我正在学习的材料介绍了区分联合和记录类型。我已经审查了两者的材料,但我仍然不清楚为什么我们会使用其中一种。
我创建的大多数玩具示例似乎都可以在两者中实现。记录似乎非常接近我认为的 C# 中的对象,但我试图避免依赖映射到 c# 作为理解 F# 的一种方式
所以...
是否有明确的理由使用其中一种?
是否存在某些适用的典型案例?
是否有某些功能在其中一个可用,而另一个则没有?
在继续讨论复杂示例之前,我试图弄清 F# 的基础知识。我正在学习的材料介绍了区分联合和记录类型。我已经审查了两者的材料,但我仍然不清楚为什么我们会使用其中一种。
我创建的大多数玩具示例似乎都可以在两者中实现。记录似乎非常接近我认为的 C# 中的对象,但我试图避免依赖映射到 c# 作为理解 F# 的一种方式
所以...
是否有明确的理由使用其中一种?
是否存在某些适用的典型案例?
是否有某些功能在其中一个可用,而另一个则没有?
把它想象成一个记录是“和”,而一个有区别的联合是“或”。这是一个字符串和一个 int:
type MyRecord = { myString: string
myInt: int }
虽然这是一个字符串或 int 值,但不能同时是两者:
type MyUnion = | Int of int
| Str of string
这个虚构的游戏可以在标题屏幕、游戏中或显示最终得分,但只有其中一个选项。
type Game =
| Title
| Ingame of Player * Score * Turn
| Endgame of Score
对由多个属性描述的复杂数据使用记录(在函数式编程理论中称为产品类型),例如数据库记录或某些模型实体:
type User = { Username : string; IsActive : bool }
type Body = {
Position : Vector2<double<m>>
Mass : double<kg>
Velocity : Vector2<double<m/s>>
}
对可以枚举的数据可能值使用区分联合(称为总和类型)。例如:
type NatNumber =
| One
| Two
| Three
...
type UserStatus =
| Inactive
| Active
| Disabled
type OperationResult<'T> =
| Success of 'T
| Failure of string
请注意,可区分联合值的可能值也是互斥的——操作的结果可以是Success
or 或 a Failure
,但不能同时是两者。
您可以使用记录类型对操作的结果进行编码,如下所示:
type OperationResult<'T> = {
HasSucceeded : bool
ResultValue : 'T
ErrorMessage : string
}
但是万一操作失败,就ResultValue
没有意义了。因此,这种类型的可区分联合版本上的模式匹配将如下所示:
match result with
| Success resultValue -> ...
| Failure errorMessage -> ...
而且,如果您与我们的操作类型的记录类型版本进行模式匹配,则意义不大:
match result with
| { HasSucceeded = true; ResultValue = resultValue; ErrorMessage = _ } -> ...
| { HasSucceeded = false; ErrorMessage = errorMessage; ResultValue = _ } -> ...
它看起来冗长而笨拙,而且可能效率也较低。我认为当你有这样的感觉时,可能暗示你使用了错误的工具来完成任务。
如果您来自 C#,您可以将记录理解为具有附加值的密封类:
有区别的联合编码替代方案,例如
type Expr =
| Num of int
| Var of int
| Add of Expr * Expr
| Sub of Expr * Expr
上面的DU读法如下:一个表达式要么是一个整数,要么是一个变量,要么是两个表达式的加法,要么是两个表达式之间的减法。这些情况不能同时发生。
您需要所有字段来构建记录。您还可以在记录中使用 DU,反之亦然
type Name =
{ FirstName : string;
MiddleName : string option;
LastName : string }
上面的示例显示中间名是可选的。
在 F# 中,您通常开始使用元组或记录对数据进行建模。当需要高级功能时,您可以将它们移动到类中。
另一方面,有区别的联合用于模拟案例之间的替代和互斥关系。
理解 DU 的一种(稍微有缺陷的)方法是将其视为花哨的 C#“联合”,而记录更像是一个普通对象(具有多个独立字段)。
另一种看待 DU 的方法是将 DU 视为两级类层次结构,其中顶级 DU 类型是抽象基类,DU 的案例是子类。这个视图实际上接近于实际的 .NET 实现,尽管这个细节被编译器隐藏了。