【问题标题】:When to use a Discriminate Union vs Record Type in F#何时在 F# 中使用区分联合与记录类型
【发布时间】:2013-06-21 22:21:01
【问题描述】:

在继续讨论复杂的示例之前,我试图弄清 F# 的基础知识。我正在学习的材料介绍了区分联合和记录类型。我已经查看了这两种材料的材料,但我仍然不清楚为什么我们会使用一种而不是另一种。

我创建的大多数玩具示例似乎都可以在两者中实现。记录似乎与我认为的 C# 中的对象非常接近,但我试图避免依赖映射到 c# 来理解 F#

所以...

  • 是否有明确的理由使用其中一个?

  • 是否存在适用的某些典型案例?

  • 是否有某些功能可用,但不是 其他的?

【问题讨论】:

标签: f# record discriminated-union


【解决方案1】:

把它想象成一个记录是“和”,而一个有区别的联合是“或”。 这是一个字符串和一个 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

【讨论】:

  • 那么,在DU中有没有办法创建一个扩展Game的组合类型呢?例如 | InGameTitle of Title * Ingame。即包含 Title * Player * Score * Turn 的元组
  • @Chris :这只是引出了一个问题:你为什么想要
  • @ildjarn 你说的很对。当我第一次写那条评论时,我对 DU 的目的并没有完全理解。 MisterMetaphor 的回答帮助我理解了为什么你会像罗伯特那样使用它,而不是像我概述的那样。
【解决方案2】:

如果您来自 C#,您可以将记录理解为具有附加值的密封类:

  • 默认不可变
  • 默认结构相等
  • 易于模式匹配

有区别的联合编码 alternatives 例如

type Expr =
    | Num of int
    | Var of int 
    | Add of Expr * Expr 
    | Sub of Expr * Expr

上面的DU读作如下:一个表达式是或者一个整数,或者一个变量,或者两个表达式的相加or 两个表达式之间的减法。这些情况不能同时发生。

您需要所有字段来构建记录。您也可以在记录中使用 DU,反之亦然

type Name =
    { FirstName : string;
      MiddleName : string option;
      LastName : string }

上面的例子表明中间名是可选的。

在 F# 中,您通常开始使用元组或记录对数据进行建模。当需要高级功能时,您可以将它们移动到类中。

另一方面,可区分联合用于模拟备选方案和案例之间的互斥关系。

【讨论】:

  • 谢谢。这个答案和另一个答案都指向了DU是一种OR关系的事实。但据了解,单个 DU 可以包含多个值。即 type Name 可以具有 FirstName , MiddleName, and LastName 的值。这仍然让我有点不确定,对于所有字段都有值的记录和对于所有字段都有值的 DU 之间有什么区别。是不是 DU 可以做某种形式的推理或操作记录不能?还是不可变属性是这里的区别?
【解决方案3】:

将记录(在函数式编程理论中称为产品类型)用于由多个属性描述的复杂数据,例如数据库记录或某些模型实体:

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

请注意,可区分联合值的可能值也是互斥的——操作的结果可以是SuccessFailure,但不能同时是两者。

您可以使用记录类型对操作的结果进行编码,如下所示:

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 = _ } -> ...

它看起来冗长而笨拙,而且可能效率也较低。我认为,当您有这种感觉时,可能暗示您使用了错误的工具来完成任务。

【讨论】:

  • 感谢您的回复。我现在看到了 DU 在哪里特别有意义。
【解决方案4】:

理解 DU 的一种(略有缺陷的)方法是将其视为花哨的 C#“联合”,而记录更像是一个普通对象(具有多个独立字段)。

查看 DU 的另一种方法是将 DU 视为两级类层次结构,其中顶级 DU 类型是抽象基类,DU 的案例是子类。这个视图实际上接近于实际的 .NET 实现,尽管这个细节被编译器隐藏了。

【讨论】:

  • 与 OO 继承层次结构的一个重要区别是 DU 的不同情况仅仅是标签,而不是不同的(子)类型。这有时会让新手感到困惑。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-04
  • 1970-01-01
  • 2020-08-22
  • 1970-01-01
  • 2011-11-12
相关资源
最近更新 更多