函数式编程与面向对象的编程


783

到目前为止,我主要接触过OO编程,并期待学习一种功能语言。我的问题是:

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



1
相似的问题cs.se也关闭了函数式编程比命令式样式提供更好结果的示例。传统观点似乎是,一个不优于另一个,或者它们在简单标准上不具有可比性,或者它们用于不同的目的...函数式编程具有更多的科学/学术渊源和用途,在工业中不那么普遍,因此该问题还引发了“行业与学术界”无法解决的观点/冲突。一位经典的裁判在函数式编程中表现出OOP风格,SICP书/ MIT
vzn 2014年

OO通过封装活动部件使代码易于理解。FP 通过减少活动部件使代码易于理解。 ” --Micheal Feathers,2010年
jaco0646,

Answers:


1192

什么时候选择功能编程而不是面向对象?

当您预计会有另一种软件演变时:

  • 当您对事物进行固定的操作时,面向对象的语言非常有用,并且随着代码的发展,您主要添加新的事物。这可以通过添加实现现有方法的新类来完成,而现有类则不理会。

  • 当您拥有固定的事物集时,功能语言是很好的选择 ,并且随着代码的发展,您主要在现有事物上添加新的操作。这可以通过添加使用现有数据类型进行计算的新函数来完成,而现有函数则不予考虑。

当进化走错路时,您就会遇到问题:

  • 向面向对象的程序添加新操作可能需要编辑许多类定义以添加新方法。

  • 向功能程序中添加新事物可能需要编辑许多功能定义以添加新案例。

这个问题已经众所周知很多年了。1998年,菲尔·沃德勒Phil Wadler)将其称为“表达问题”。尽管一些研究人员认为表达问题可以通过混合函数之类的语言功能解决,但尚未得到广泛接受的解决方案成为主流。

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

功能语言擅长以树形形式处理符号数据。最喜欢的例子就是编译器,在源和中间语言改变很少(几乎相同的事情),但是编译器作者一直在增加新的翻译和代码改进或优化(对事物的新操作)。编译和翻译通常是功能语言的“杀手级应用”。


119
这个答案背后有些严肃的禅意。我认为这说明了某些OOP设计模式(访问者)实际上是黑客,试图克服添加新操作的问题。
Jacobs Data Solutions

54
在JavaScript中,您可以拥有所有东西。
Erik Reppen

61
@ErikReppen在什么时候出现问题,何时选择使用功能功能,何时选择使用面向对象的功能?
Norman Ramsey 2013年

7
@NormanRamsey在JS中混合使用它并不罕见,一流的功能与许多与JS OOP相关的功能相关。JS的数组排序以arg为函数,可以产生一些强大的数据结构。闭包+一个传递的函数用于使jquery对象保持非常轻量的内存,因为大多数方法只是引用。等等...
Erik Reppen

9
@NormanRamsey:非常不错的答案,类似于SICP。根据此分类,功能和过程编程在面向对象编程的相对侧组合在一起。这可以解释OOP在1980年代后期到1990年代初期的繁荣时期:当GUI成为主流时,OOP被证明是对其建模的一种好方法,因为您通常具有一组固定的操作(绘画,打开,关闭,调整大小)。和越来越多的小部件。当然,正如您所说明的,这并不意味着OOP优于任何应用程序的过程。
乔治

176

您不必在两个范式之间进行选择。您可以使用许多功能概念来编写具有OO体系结构的软件。FP和OOP本质上是正交的

以C#为例。您可以说这主要是面向对象的,但是有许多FP概念和构造。如果您考虑使用Linq,则允许Linq存在的最重要的构造实际上是功能性的:lambda表达式

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

许多现代语言是多种范例。

推荐读物

因为我在同一条船上(OOP背景,学习FP),所以建议您阅读一些我非常赞赏的读物:


8
@duffymo:您的评论,无论您采用哪种方式,都毫无意义。没人希望对语言,虚拟机或平台进行比较。
布鲁诺·里斯

6
@Bruno-对“ .NET功能”的响应。放松。
duffymo's

6
有趣的是,我看不到你责怪Dykam关于他毫无意义的评论。当我不在时,您是否被提名为主持人?除了你,没人在这里发火。我再说一遍-放松。
duffymo's

4
嘿,对不起,如果我开始燃烧。我并不是说其他​​平台的功能不那么强大,只是.NET不仅仅支持OOP。例如,它具有尾叫优化功能。
Dykam

5
@nawfal,直到您可以指出两个范例中的某些固有特征并说它们是不兼容的,然后它们才是正交的:这是讨论的核心。FP从定义上讲与命令式编程不兼容,但是OOP不限于命令式编程。我们对这些概念使用不同的词的原因是,以便我们可以讨论它们:将它们组合在一起时,您只是在强迫我们不必要地提出新词。
DavidS '16

31

面向对象编程提供:

  1. 封装,以
    • 内部状态控制突变
    • 限制耦合到内部表示
  2. 子类型化,允许:
    • 兼容类型的替换(多态)
    • 在类之间共享实现的粗略方法(实现继承)

在Haskell或Scala中,函数式编程可以通过类型类的更通用机制进行替换。不鼓励或禁止使用可变的内部状态。内部表示的封装也可以实现。参见Haskell与OOP进行比较。

诺曼的断言“在功能程序中添加新事物可能需要编辑许多功能定义以添加新案例”。取决于功能代码使用类型类的程度。如果将特定抽象数据类型上的模式匹配分布在整个代码库中,则您确实会遭受此问题的困扰,但是从一开始它可能是一个较差的设计。

编辑讨论类型类时删除对隐式转换的引用。在Scala中,类型类是使用隐式参数而不是转换进行编码的,尽管隐式转换是实现兼容类型替换的另一种方法。


3
类型类不是隐式转换为其他类型的机制。它们是为一种类型定义的一组功能的描述,以便提供一种多态性。尽管Haskell类型类有一些重要的区别,但与Java风格的OOP最接近的是接口。
扎克2010年

25
  1. 如果您处于高度并发的环境中,那么纯函数式编程将很有用。缺少可变状态使得并发几乎变得微不足道。参见Erlang。

  2. 在多范式语言中,如果可变状态的存在必须是实现细节,那么您可能需要对某些事物进行功能建模,因此FP是解决问题领域的良好模型。例如,请参阅Python中的列表推导或D编程语言中的std.range。这些是受函数编程启发的。

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.