什么是 F# 中的可区分联合以及我们在 OOP 中有什么类型的替代方案

Tor*_*eli 5 .net f# functional-programming discriminated-union

我正在从 C# 进入函数式编程。当然,由于我对 C# 的深入了解,我选择了 F# 作为我的第一门函数式语言,并试图花时间学习它。

现在我正处于需要了解什么是歧视工会以及为什么它很重要以及为什么我们实际上需要它的步骤?!

我真的做了很多研究

但是导师、讲师、文章和博客文章的问题在于,人们实际上试图用大量函数式编程术语来描述/教我们歧视联合,这对我们来说当然是非常不可理解的,整个背景都是 OOP 和只需一点 LINQ、表达式和高阶函数。

我是函数式世界的新手,我的大脑充满了 OOP 思维方式,所以很难从这个角度理解这个概念。

如果你真的谷歌它,你会得到那种类型的回应:

Discriminated Unions # 您可以组合单例类型、联合类型、类型保护和类型别名来构建称为可区分联合的高级模式,也称为标记联合或代数数据类型。可区分联合在函数式编程中很有用。

这在我看来真的没有任何意义。所以请以人性和正常的方式告诉我什么是歧视联盟,为什么我们需要它?什么可以与 OOP 世界相比?(因为它真的会帮助我)

谢谢你。

Fyo*_*kin 7

OOP 世界并没有真正严格模拟 DU(这就是为什么它经常被发音为“缺陷”的原因),但最接近的是两级继承层次结构。

考虑以下 DU:

type Shape = Circle of radius:Float | Rectangle of width:Float * height:Float
Run Code Online (Sandbox Code Playgroud)

这种类型的语义(即“意义”)可以这样模糊地表述:形状有两种形式 -Circle具有半径,和Rectangle具有宽度和高度,并且没有其他类型的形状

大致相当于以下继承层次结构:

abstract class Shape {}

class Circle : Shape { 
    public double Radius { get; set; }
}

class Rectangle : Shape {
    public double Width { get; set; }
    public double Height { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

这个 C# 代码片段也模糊地表达了“形状有两种风格 -CircleRectangle”的想法,但有一些重要的区别:

  1. 将来(或在其他库中),可能会出现更多种类的形状。其他人可能只是声明一个继承自的新类Shape- 就这样。F# 歧视联合不允许这样做。

  2. Circle并且Rectangle是它们自己的类型。这意味着可以声明一个接受 aCircle而不是 a 的方法Rectangle。F# 歧视联合不允许这样做。在F#,Shape是一种类型,但CircleRectangle没有类型。不能有类型为 的变量、参数或属性Circle

  3. F# 提供了许多用于处理 DU 的简化句法结构,在 C# 中必须非常冗长地编写这些结构,并且有很多噪音。


表面上的第(1)和(2)点似乎是限制(实际上,我在两种情况下都使用了“不允许”一词),但这实际上是一个特征。这个想法是一些限制(不是全部)导致更正确、更稳定的程序。回顾过去:“goto 被认为是有害的”,引用替换了指针,垃圾回收替换了手动内存管理——所有这些都带走了一些灵活性,有时甚至是性能,但通过大大提高代码的可靠性来弥补这一点。

F# DU 也是如此:事实上,除了Circleand之外,可能没有其他类型的形状Rectangle,允许编译器检查所​​使用的函数的正确性Shape- 即验证所有可能的情况都已处理。如果您稍后决定添加第三种形状,编译器将有助于找到需要处理这种新情况的所有位置。


第三点是“使正确的事情变得容易,错误的事情变得困难”的想法。为此,F# 提供了一些有用的语法(例如模式匹配)和一些有用的默认值(例如不变性、结构比较),这些在 C# 中必须手动编码并严格执行。


Tom*_*cek 6

有区别的联合有点像 OOP 中的类层次结构。OOP 中的经典示例类似于动物,可以是狗或猫。在 OOP 中,您可以将其表示为具有一些抽象方法(例如MakeAnimalNoise)和狗和猫的具体子类的基类。

在函数式编程中,匹配的事物是Animal有两种情况的可区分联合:

type Animal =  
  | Dog of breed:string
  | Cat of fluffynessLevel:int
Run Code Online (Sandbox Code Playgroud)

在 OOP 中,您有虚拟方法。在 FP 中,您可以使用模式匹配将操作编写为函数:

let makeAnimalNoise animal = 
  match animal with
  | Dog("Chihuahua") -> "woof sqeek sqeek woof"
  | Dog(other) -> "WOOF"
  | Cat(fluffyness) when fluffyness > 10 -> "MEEEOOOOW"
  | Cat(other) -> "meow"
Run Code Online (Sandbox Code Playgroud)

FP 和 OOP 方法之间有一个重要区别:

  • 使用抽象类,您可以轻松添加新案例,但添加新操作需要修改所有现有类。
  • 使用可区分联合,您可以轻松添加新操作,但添加新案例需要修改所有现有函数。

如果您来自 OOP 背景,这可能看起来很奇怪。在讨论 OOP 中的类时,每个人都强调需要可扩展性(通过添加新类)。在实践中,我认为你需要两者 - 所以你选择哪个方向并不重要。FP 有它的好处,就像 OOP 一样(有时)。

当然,这是一个完全无用的例子。有关这在实践中如何有用的更现实的讨论,请参阅 Scott Wlaschin 出色的类型设计系列