FP 和 OO 是正交的吗?

我一次又一次地听到这种说法,我试图理解并验证 FP 和 OO 是正交的这一观点。

首先,两个概念是正交的,这意味着什么?

FP 鼓励尽可能多的不变性和纯粹性,而 OO 似乎是为状态和突变而建立的——一个稍微有组织的命令式编程?我意识到对象可以是不可变的,但是 OO 对我来说似乎意味着状态/变化。

他们看起来是对立的,这对他们的正交性有什么影响?

像 Scala 这样的语言使得 OO 和 FP 都很容易实现,这会影响这两种方法的正交性吗?

9289 次浏览

正交性意味着两件事情是不相关的。它来自数学,意思是 垂直的。在通常的用法中,它可以表示两个决定是不相关的,或者当考虑另一个主题时,一个主题是不相关的。在这里,正交意味着一个概念不暗示或排除另一个。

面向对象程序设计函数式程序设计这两个概念并不矛盾。面向对象并不意味着可变性。许多以传统方式接触到面向对象程序的人通常首先使用 C + + 、 Java、 C # 或类似的语言,这些语言的可变性是常见的,甚至是受到鼓励的(标准库提供了各种各样的可变类供人们使用)。因此,很多人将面向对象编程与命令式编程和可变性联系起来是可以理解的,因为他们就是这样学习的。

然而,面向对象编程涵盖了以下主题:

  • 封装
  • 多态性
  • 抽象

这些都不意味着可变性,也不排除函数式编程。因此,是的,它们是 正交,因为它们是不同的概念。它们并不是对立的——你可以使用一个,或者另一个,或者两者都使用(或者两者都不使用)。像 Scala 和 F # 这样的语言试图将两种范例结合成一种语言:

Scala 是一个集成了 面向对象程序设计和函数式编程特性的多重编程范式。

来源

F # 是一个简洁、高效的 功能性的面向对象语言。NET,帮助您编写简单的代码来解决复杂的问题。

来源

对象的概念可以以一种不可变的方式实现。一个例子是 Abadi 和 Cardelli 写的书“ 物体理论”,该书旨在将这些思想形式化,对象首先被赋予不可变的语义,因为这使得面向对象程序的推理更加简单。

在这种情况下,传统上会就地修改对象的方法将返回一个新对象,而以前的对象将保持不变。

首先,两个概念是正交的,这意味着什么?

这意味着它们不会相互影响,也就是说,函数式语言的功能性不会因为它也是面向对象的而降低。

他们看起来是对立的,这对他们的正交性有什么影响?

如果它们是对立的(也就是说,一个纯函数式语言不可能是面向对象的) ,那么它们根据定义就不是正交的。然而,我不相信这是事实。

面向对象似乎是为状态和突变而建立的(略微有组织的命令式编程?).我知道对象是不可变的。但对我来说,OO 似乎意味着状态/变化。

虽然对于大多数主流 OO 语言来说都是这样,但是 OO 语言没有理由需要具有可变状态。

如果一种语言具有对象、方法、虚继承和 ad-hoc 多态性,那么它就是一种面向对象的语言——无论它是否具有可变状态。

首先,两个概念是正交的,这意味着什么?

这意味着这两个概念没有对立的思想或不互相矛盾。

FP 鼓励尽可能的不变性和纯洁性。面向对象似乎是为状态和突变而建立的(略微有组织的命令式编程?).我知道对象是不可变的。但对我来说,OO 似乎意味着状态/变化。

他们看起来是对立的,这对他们的正交性有什么影响?

像 Scala 这样的语言使得 OO 和 FP 都很容易实现,这会影响这两个方法的正交性吗?

面向对象是关于封装、对象组合、抽象化、通过子类型实现的多态性,以及 必要时控制变异(面向对象也鼓励不变性)。FP 是关于复合函数、控制抽象和约束多态性(亦称参数多态)。因此,这两种观点并不矛盾。它们都为您提供了不同种类的能力和抽象机制,这些在一种语言中当然是可能的。事实上,这就是构建 斯卡拉的论文!

在 Google 的 Scala 实验演讲中,Martin Odersky 很好地解释了他是如何相信 OO 和 FP 这两个概念是相互正交的,以及 Scala 是如何优雅地、无缝地将这两个范例统一到一个新的范例中,这个范例在 Scala 社区中被称为对象函数范例。必须看着你说话。:-)


对象函数语言的其他示例: 奥卡姆F # Nemerle

对于两个概念是正交的意思是他们可以独立地实现到任何程度在任何给定的表现形式。举例来说,考虑到音乐,你可以根据一首乐曲的和声程度和节奏程度来对它进行分类。“和声”和“节奏”这两个概念是正交的,即有和声和节奏片段、不和声和节奏片段,也有不和声和节奏片段以及和声和节奏片段。

应用到最初的问题,这意味着有纯粹的函数式,非面向对象的编程语言,如 Haskell,纯粹的面向对象,“非函数式”的语言,如 Eiffel,但也有语言,如 C 和语言都不是,如 Scala。

简单地说,Scala 是面向对象的,这意味着你可以定义数据结构(“类”和“ trait”) ,用操作这些数据的方法封装数据,保证这些结构的实例(“对象”)始终处于定义状态(对象的契约在它的类中布置)。

另一方面,Scala 是一种函数式语言,这意味着它更倾向于不可变而不是可变的状态,而且函数是第一类对象,它可以像其他对象一样作为局部变量、字段或其他函数的参数使用。除此之外,Scala 中几乎每个语句都有一个值,这鼓励您使用函数式编程风格。

Scala 中面向对象编程和函数式编程的正交性还意味着,作为一个程序员,您可以自由地选择这两个概念中的任何一个。你可以用一种纯命令式的方式来编写程序,只使用可变对象,而不使用函数作为对象,另一方面,你也可以在 Scala 中编写纯函数式的程序,而不使用任何面向对象的特性。

Scala 实际上并不要求您使用这种或那种样式。它允许您在两个世界中选择最佳方案来解决您的问题。

像所有分类一样,将编程语言划分为函数式、面向对象、过程式等是虚构的。但是我们确实需要分类,在编程语言中,我们根据一组语言特征和使用语言的人的哲学方法进行分类(后者受前者的影响)。

因此,有时“面向对象”语言可以成功地采用“函数式”编程语言的特性和原理,反之亦然。但是当然不是所有的编程语言特性和原理都是兼容的。

例如,像 OCaml 这样的函数式语言通过词法作用域和闭包来实现封装,而面向对象语言使用公共/私有访问修饰符。这些机制本身并不是不兼容的,但是它们是多余的,而且像 F # (一种主要是函数式语言,寻求与面向对象的语言和谐相处)这样的语言。NET 库和语言栈)必须不遗余力地弥合这一差距。

另一个例子是,OCaml 使用结构化类型系统来面向对象,而大多数面向对象语言使用名义类型系统。它们几乎是不兼容的,有趣的是,它们代表了面向对象语言领域内的不兼容性。

您可以将函数实现为对象,将对象实现为函数的集合,因此这两个概念之间显然存在某种关系。

FP 鼓励尽可能的不变性和纯洁性

您正在谈论的是 纯粹的函数式编程。

而 OO 似乎是为国家和变异而生的

对象不需要是可变的。我认为对象和变异是正交的概念。例如,OCaml 编程语言提供了纯函数式对象更新的语法。

像 Scala 这样的语言使得同时做 OO 和 FP 变得很容易

没有。缺乏尾部调用优化意味着大多数惯用的纯函数式代码将在 Scala 中堆栈溢出,因为它会泄漏堆栈帧。例如,连续传球式连续传球式(CPS)和 Bruce McAdam 在论文 差不多结束了中描述的所有技术。要解决这个问题并不容易,因为 JVM 本身不能进行尾部调用优化。

关于纯函数式编程和面向对象编程的正交性,我想说它们至少接近于正交,因为纯函数式编程只处理小程序(例如高阶函数) ,而面向对象编程处理程序的大规模结构。这就是为什么函数式编程语言通常为大规模结构化提供一些其他的机制,例如 Standard ML 和 OCaml 的高阶模块系统,Common Lisp 的 CLOS 或 Haskell 的类型类。

帮助我理解 FP 和 OO 之间关系的一件事是 SICP 的书,特别是 函数程序的模块化和对象的模块化部分。如果你正在思考这些问题,并且你有一个空闲的周末,那么通读前三章可能是值得的,它让你大开眼界。

正交的。听起来不错。如果你受过教育,你可以把它绑起来假装一下。这有点像范例。

这完全取决于您所处的圈子,以及每种类型的编程技术将带给您什么。我读过一些关于 SS 的文章,大多数来自函数式编程语言的人通常坚持认为,你只能使用函数式编程,其他任何东西都与思维和思维模式背道而驰。

面向对象编程主要是关于捕获状态,并尽可能保持这种状态的本地化,以免受到任何不属于您用来管理状态的对象的影响。另一方面,函数式编程从不同的角度看待状态问题,试图将状态与系统分离,并将其简化为函数。是的,您可以在代码中使用这两种技术,但是它们都从不同的角度看待软件设计。

函数式编程技术已经引起了人们的极大兴趣,主要是因为在处理多核芯片和并行编程时需要对状态进行管理。目前看来,函数式编程在处理这个问题上确实占据了上风,但是您可以使用 Object 实现同样的效果。你只是换个角度看问题。与其挠头,试图摆脱尽可能多的状态,你看看在设计中的对象,看看你如何能把他们配对到什么核心,他们应该做什么,使用设计模式,CRC 和对象分析。然而,对象确实进入了这个领域,而函数式编程要困难得多的地方在于分析现实世界,并将其映射到一个可以理解的计算机系统。例如,在 OO 中,person 对象将是状态的封装,其中包含作用于人员状态的方法。在函数式编程中,人将被分解为作用于人数据的数据部分和函数,并附加了一个附加条件,即数据应该只创建一次并且是不可变的。

我必须承认,虽然我来自 OO 背景,但在大多数 OO 语言中,当处理多核芯片时,我采用了函数式的方式,主要是通过核心编程设计结构(如线程和委托)和传递伪数据对象。这使我对面向对象编程的技术产生了疑问,因为它似乎不能很好地映射到这种线程化设计。

我刚刚发现了一个美妙的 解释的正交 OOP 和 FP。

基本思路如下。想象一下,我们正在处理数学表达式的 AST。所以我们有不同类型的名词(常量、加法、乘法)和不同的动词(eval、 toString)。

假设表达式是 (1 + 2) * 3,那么 AST 就是:

multiplication
/        \
addition      3
/    \
1      2

要实现一个动词,我们必须为每种类型的名词提供它的实现。我们可以把它表示为一张表:

                +---------------------+-------------------------------------+
| eval                | toString                            |
+---------------+---------------------+-------------------------------------+
| constant      | value               | value.toString                      |
+---------------+---------------------+-------------------------------------+
| addition      | lhs.eval + rhs.eval | lhs.toString + " + " + rhs.toString |
+---------------+---------------------+-------------------------------------+
| mutiplication | lhs.eval * rhs.eval | lhs.toString + " * " + rhs.toString |
+---------------+---------------------+-------------------------------------+

“正交性”来自于这样一个事实,在 中,我们将实现这个表 一排排的: 我们将把每个名词表示为一个类,它必须实现每个方法。

另一方面,在 FP中,我们将实现这个表 用柱子ーー我们将为每个动词编写一个函数,这个函数将对不同类型的参数作出不同的反应(可能使用模式匹配)。