函数式编程 vs 面向对象编程

850

迄今为止,我主要接触过面向对象编程,现在期待学习一门函数式编程语言。我的问题是:

  • 你在什么时候选择函数式编程而不是面向对象编程?
  • 在哪些典型问题定义中,函数式编程是更好的选择?

重复:https://dev59.com/w3RB5IYBdhLWcg3wr44U - gnovice
这是相关的:http://programmers.stackexchange.com/questions/9730/functional-programming-vs-oop - kirill_igum
1
类似的问题在cs.se也被关闭了函数式编程何时比命令式风格更好的例子是什么。传统智慧似乎是一个不优于另一个,或者它们不能用简单的标准进行比较,或者它们用于不同的目的...函数式编程具有更多的科学/学术起源和用途,在工业界中较少见,因此这个问题也建立了一个“工业界与学术界”的无法解决的观点/冲突。一个经典的参考书《计算机程序的构造和解释》展示了函数式编程中的面向对象编程风格,该书由MIT出版。 - vzn
“OO通过封装移动部分使代码易于理解。FP通过最小化移动部分使代码易于理解。” --Micheal Feathers, 2010 - jaco0646
4个回答

1305
当你预见到一种不同类型的软件演进时:
* 当您在事物上有一组固定的操作,并且随着您的代码发展,您主要添加新事物时,面向对象的语言非常好。这可以通过添加实现现有方法的新类来实现,而现有类则保持不变。 * 当您在事物上有一组固定的东西,并且随着您的代码发展,您主要添加对现有事物的新操作时,函数式语言非常好。这可以通过添加使用现有数据类型计算的新函数来实现,而现有函数则保持不变。
当演进走错路时,您会遇到问题:
* 向面向对象的程序添加一个新操作可能需要编辑许多类定义以添加新方法。 * 向函数式程序添加一种新类型的东西可能需要编辑许多函数定义以添加新情况。
这个问题已经被广泛知晓多年; 1998年,Phil Wadler将其称为“表达问题”。尽管一些研究人员认为可以使用混入等语言特性来解决表达问题,但广泛接受的解决方案尚未进入主流。
针对以下典型问题,函数式编程更好地解决:

什么是函数式编程更好的选择的典型问题定义?

函数式语言在操作树形符号数据方面表现出色。编译器是一个常见的例子,其中源语言和中间语言很少改变(大多数情况下是相同的“事物”),但编译器编写人员始终在添加新的翻译和代码改进或优化(对“事物”的新操作)。编译和翻译更普遍地说是函数式语言的“杀手级应用程序”。

138
这个答案背后有一些严肃的禅意。我认为它揭示了某些面向对象设计模式(访问者)实际上是试图克服添加新操作问题的Hack。 - Jacobs Data Solutions
66
在 JavaScript 中,你可以拥有所有的事情。 - Erik Reppen
69
在这一点上,问题就出现了:你何时选择使用功能特性,何时选择使用面向对象的特性? - Norman Ramsey
7
@NormanRamsey,混淆JS是非常普遍的事情,一级函数与很多JS面向对象程序设计相关的特性息息相关。JS的数组排序采用函数作为参数,从而能够生成一些强大的数据结构。闭包+传递的函数被用来保持jQuery对象在内存方面非常轻量化,因为大多数方法只是引用。等等... - Erik Reppen
12
@NormanRamsey的回答非常好,类似于《计算机程序的构造和解释》一书的思路。根据这种分类法,函数式编程和过程式编程被归为面向对象编程的对立面。这可以解释为什么在20世纪80年代末90年代初GUI变得流行时,OOP迅速兴起:因为通常有一组固定的操作(绘图、打开、关闭、调整大小)和越来越多的小部件需要建模。当然,这并不意味着OOP比过程式编程更适合任何应用,正如你所举的例子。 - Giorgio
显示剩余4条评论

191

您不必在两种范例之间做出选择。您可以使用许多功能概念编写具有OO架构的软件。 FP和OOP在本质上是正交的

以C#为例。你可以说它主要是面向对象的,但有许多FP概念和结构。如果你考虑Linq,允许存在Linq的最重要的结构是功能性的: lambda表达式

另一个例子是F#。你可以说它主要是FP的,但有许多OOP概念和结构可用。你可以定义类、抽象类、接口、处理继承。甚至当代码更清晰或显著提高性能时,你可以使用可变性。

许多现代语言都是多范例的。

推荐阅读

由于我处于同样的情况(OOP背景,学习FP),我建议您阅读我非常欣赏的一些文章:


1
不要忘记,您可以混合使用 F# 和 C# 代码,以便利用它们各自的优点。 - Paolo
1
你的回答是展示 .Net 强大能力的绝佳例子。它展示了它能够利用两种范式的优势。 - Dykam
5
抱歉如果我引起了某些争论。我的意思不是说其他平台不够强大,而是 .NET 不仅支持面向对象编程。例如,它具有尾调用优化。 - Dykam
3
我认为函数式编程和面向对象编程并不是正交的。使用函数式构造并不等同于函数式编程。当然,像LINQ中的lambda表达式这样使用函数式构造会使它不太面向对象,但它仍然不是函数式编程。函数式编程是指函数是一等公民的语言,而面向对象编程是指类是一等建筑块(或类似的概念——我意识到有许多种面向对象编程)。在我看来,正确的说法应该是,“有一些多范式语言可以让你编写既包含面向对象构造又包含函数式构造的代码,从而使得两者都不太面向对象也不太函数式编程”。 - nawfal
7
@nawfal,在你能指出这两种范式中的某些内在特征并说它们是不兼容的之前,它们是正交的:这是讨论的核心。FP根据定义与命令式编程不兼容,但OOP并不局限于命令式编程。我们之所以有不同的词来描述这些概念,是为了能够谈论它们:当你把它们混为一谈时,你只是强迫我们不必要地想出新词汇。 - DavidS
显示剩余3条评论

33

面向对象编程提供:

  1. 封装,以
    • 控制内部状态的变化
    • 限制对内部表示的耦合
  2. 子类型,允许:
    • 可互换类型(多态)的替换
    • 在类之间共享实现的粗糙手段(实现继承)

在Haskell甚至Scala中,函数式编程可以通过更一般的类型类机制进行替换。可变内部状态要么被不鼓励或禁止。还可以实现内部表示的封装。请参阅 Haskell vs OOP 进行良好的比较。

Norman声称“向函数式程序添加新类型可能需要编辑多个函数定义以添加新情况。”这取决于函数式代码如何使用类型类。如果在代码库中分布了特定抽象数据类型的模式匹配,那么您确实会遇到此问题,但这可能是一个设计失误。

已编辑 在讨论类型类时删除了隐式转换的引用。在Scala中,类型类是用隐式参数编码的,而不是转换,虽然隐式转换是实现可互换类型的另一种手段。


3
Typeclasses 不是将一种类型隐式地转换为另一种类型的机制。它们是用于为某个类型定义一组函数以提供一种多态形式的描述。最接近 Java 面向对象编程中接口的概念,但 Haskell 的 Typeclasses 有一些重要的区别。 - Zak

28
  1. 如果您处于高度并发的环境中,那么纯函数式编程是很有用的。缺乏可变状态使得并发几乎变得微不足道。请查看 Erlang。

  2. 在多范式语言中,如果可变状态的存在只是实现细节,因此函数式编程是问题领域的一个好模型。例如,请参见 Python 中的列表推导式或 D 编程语言中的 std.range。它们受到函数式编程的启发。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接