代数数据类型可以解决什么问题?


18

公平的警告,我是函数编程的新手,所以我可能有很多错误的假设。

我一直在学习代数类型。许多功能语言似乎都有它们,它们在与模式匹配一​​起使用时非常有用。但是,他们实际上解决了什么问题?我可以像这样在C#中实现一个看似(排序)的代数类型:

public abstract class Option { }
public class None : Option { }
public class Some<T> : Option
{
    public T Value { get; set; }
}

var result = GetSomeValue();
if(result is None)
{
}
else
{
}

但是我认为大多数人都同意这是面向对象编程的混蛋,您永远都不要这样做。那么函数式编程是否只是添加了一种更简洁的语法,从而使这种编程风格看起来不太那么粗拙?我还想念什么?


6
函数式编程是不同于面向对象编程的范例。
Basile Starynkevitch 2015年

我认识到@BasileStarynkevitch,但是有些语言像F#一样,两者都有。问题并不仅仅是功能语言,而是更多关于代数数据类型可以解决的问题。
ConditionRacer

7
当我确定会发生什么事class ThirdOption : Option{},给你new ThirdOption(),你预期SomeNone
阿蒙2015年

1
具有和类型的@amon语言通常可以禁止使用。例如,Haskell定义了data Maybe a = Just a | Nothing(相当于data Option a = Some a | None您的示例):您不能在事后添加第三种情况。虽然可以按照显示的方式对C#中的求和类型进行仿真,但这并不是最漂亮的。
Martijn 2015年

1
我认为它不是“ ADT解决了什么问题”,而更像是“ ADT是处理问题的另一种方式”。
MathematicalOrchid

Answers:


44

具有接口和继承的类提供了一个开放的世界:任何人都可以添加一种新的数据。对于给定的接口,可能存在在世界各地以不同文件,不同项目,不同公司实施该接口的类。它们使向数据结构中添加案例变得容易,但是由于接口的实现是分散的,因此很难向接口中添加新方法。接口公开后,基本上就冻结了。没有人知道所有可能的实现。

代数数据类型是双重的,它们是封闭的。所有数据案例都集中在一个地方,并且操作不仅可以详尽地列出变体,还鼓励他们这样做。因此,编写对代数数据类型进行操作的新函数很简单:只需编写该死的函数即可。作为回报,添加新的案例很复杂,因为您需要基本上遍历整个代码库并扩展each match。与接口的情况类似,在Rust标准库中,添加新变量是一项重大更改(对于公共类型)。

这是表达问题的两个方面。代数数据类型是对它们的不完全解决方案,但是OOP也是。两者都有优点,取决于存在多少个数据案例,这些案例更改的频率以及操作扩展或更改的频率。(这就是为什么许多现代语言同时提供这两种方法或类似方法的原因,或者直接寻求更强大,更复杂的机制来尝试同时包含这两种方法的原因。)


如果您无法更新匹配项,是否会收到编译器错误,还是仅在运行时才发现错误?
2015年

6
@Ian大多数功能语言都是静态类型的,并检查模式匹配的详尽性。但是,如果存在“全部捕获”模式,则即使函数必须处理新的情况以完成其工作,编译器也会感到满意。另外,您必须重新编译所有相关代码,不能只编译一个库并将其重新链接到已构建的应用程序中。

同样,静态检查模式是否穷举通常很昂贵。它最多可以解决SAT问题,最坏的情况是,该语言允许使用任意谓词,这使不确定性无法确定。
usr

@usr我知道没有一种语言会尝试一个完美的解决方案,要么编译器了解它是详尽无遗的,要么您被迫在崩溃和刻录时添加一个万能的案例。不过,我不知道与SAT的关系,您是否与降价有联系?无论如何,对于用真实程序编写的实际代码,详尽性检查简直是九牛一毛。

假设您要匹配N个布尔值。然后添加匹配子句,例如(a,b,_,d,...)。那么,万能的情况就是!clause1 &&!clause2 &&...。对我来说,这看起来像是SAT。
usr

12

那么函数式编程是否只是添加了一种更简洁的语法,从而使这种编程风格看起来不太那么粗拙?

也许这是一种简化,但是可以。

我还想念什么?

让我们弄清楚什么是代数数据类型(总结“ 您学习为Haskell”中的这一精妙链接):

  • 总和类型显示为“此值可以是A B”。
  • 一种产品类型,上面写着“此值既是A 是B”。

您的示例仅适用于第一个示例。

您可能缺少的是,通过提供这两个基本操作,功能语言可让您构建其他所有内容。C#在它们之上具有结构,类,枚举,泛型,以及一堆规则来控制这些事物的行为。

结合一些语法帮助,功能语言可以将操作分解为这两个路径,从而提供了一种简洁,简单而优雅的类型处理方法。

代数数据类型可以解决什么问题?

它们解决了与任何其他类型系统相同的问题:“在这里使用什么值合法?” -他们只是采取了不同的方法。


4
您的最后一句话有点像告诉某人“船舶解决了与飞机相同的问题:运输-他们只是采取了不同的方法”。这是一个完全正确的陈述,也是一个毫无用处的陈述。
Mehrdad

@mehrdad-我认为这有点夸大了。
Telastyn

1
毫无疑问,您很好地理解了代数数据类型,但是项目符号列表(“总和类型是...”和“产品类型是...”)看起来更像是对联合和交集类型的描述,与求和和乘积类型完全相同。
pyon 2015年

6

得知模式匹配不是使用Options的最惯用的方式,可能会让您感到惊讶。有关更多信息,请参见Scala选项文档。我不确定为什么这么多FP教程会鼓励这种用法。

通常,您所缺少的是创建了许多功能来简化使用Option的功能。考虑一下Scala文档中的主要示例:

val name: Option[String] = request getParameter "name"
val upper = name map { _.trim } filter { _.length != 0 } map { _.toUpperCase }
println(upper getOrElse "")

注意如何mapfilter让您在选项上链接操作,而不必在每个点都检查您是否有None。然后,最后使用getOrElse您指定默认值。您绝对不会做“粗暴”的事情,例如检查类型。所有不可避免的类型检查都在库内部进行。Haskell具有自己的一组类似功能,更不用说将对任何monad或functor起作用的大量功能。

其他代数数据类型具有它们自己的惯用方式,并且在大多数情况下,图腾柱上的模式匹配很低。同样,在创建自己的类型时,期望提供类似的功能来使用它们。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.