我一次又一次地听到这个,我试图理解和验证 FP 和 OO 是正交的想法。
首先,两个概念正交是什么意思?
FP 尽可能鼓励不变性和纯洁性,而 OO 似乎是为状态和突变而构建的——命令式编程的稍微组织化的版本?我意识到对象可以是不可变的,但 OO 似乎暗示着我的状态/改变。
他们似乎是对立的。这如何影响它们的正交性?
像 Scala 这样的语言可以很容易地同时进行 OO 和 FP,这会影响这两种方法的正交性吗?
我一次又一次地听到这个,我试图理解和验证 FP 和 OO 是正交的想法。
首先,两个概念正交是什么意思?
FP 尽可能鼓励不变性和纯洁性,而 OO 似乎是为状态和突变而构建的——命令式编程的稍微组织化的版本?我意识到对象可以是不可变的,但 OO 似乎暗示着我的状态/改变。
他们似乎是对立的。这如何影响它们的正交性?
像 Scala 这样的语言可以很容易地同时进行 OO 和 FP,这会影响这两种方法的正交性吗?
正交性意味着两件事是不相关的。它来自数学,意思是垂直。在通常的用法中,它可能意味着两个决定是不相关的,或者在考虑另一个主题时一个主题是不相关的。如此处使用的,正交意味着一个概念既不暗示也不排除另一个概念。
面向对象编程和函数式编程这两个概念并不矛盾。面向对象并不意味着可变性。许多以传统方式接触面向对象程序的人通常首先使用 C++、Java、C# 或类似的语言,其中可变性很常见,甚至受到鼓励(标准库提供了各种可变类供人们使用)。因此,许多人将面向对象编程与命令式编程和可变性联系起来是可以理解的,因为这就是他们学习它的方式。
然而,面向对象的编程涵盖了以下主题:
这些都不意味着可变性,也不排除函数式编程。所以是的,它们是正交的,因为它们是不同的概念。它们不是对立的——你可以使用一个,或者另一个,或者两者都使用(甚至都不使用)。Scala 和 F# 等语言试图将这两种范式组合成一种语言:
Scala 是一种多范式编程语言,旨在集成面向对象编程和函数式编程的特性。
F# 是一种简洁、富有表现力和高效的函数式和面向对象的.NET 语言,可帮助您编写简单的代码来解决复杂的问题。
首先,两个概念正交是什么意思?
这意味着这两个概念没有对立的想法或彼此不兼容。
FP 尽可能鼓励不变性和纯洁性。OO 似乎是为状态和突变而构建的(命令式编程的稍微组织化的版本?)。而且我确实意识到对象可以是不可变的。但 OO 似乎暗示着我的状态/改变。
他们似乎是对立的。它如何影响它们的正交性?
像 Scala 这样的语言可以很容易地实现 OO 和 FP,这会影响这两种方法的正交性吗?
OO 是关于封装、对象组合、数据抽象、通过子类型的多态性以及必要时的受控突变(OO 中也鼓励不变性)。FP 是关于函数组合、控制抽象和受约束的多态性(又名参数多态性)。因此,这两种观点并不矛盾。它们都为您提供了不同种类的权力和抽象机制,这在一种语言中当然是可能的。事实上,这就是构建Scala的论点!
在 Google 的Scala 实验演讲中,Martin Odersky 很好地解释了他如何相信这两个概念——OO 和 FP——是相互正交的,以及 Scala 如何优雅而无缝地将这两个范式统一为一个在 Scala 社区广为人知的新范式:对象功能范式。必须为你看谈话。:-)
首先,两个概念正交是什么意思?
这意味着它们不会相互影响。即,函数式语言的功能性并不低,因为它也是面向对象的。
他们似乎是对立的。它如何影响它们的正交性?
如果它们是对立的(即纯粹的函数式语言不可能是面向对象的),那么它们在定义上就不是正交的。但是,我不认为是这种情况。
OO 似乎是为状态和突变而构建的(命令式编程的稍微组织化的版本?)。而且我确实意识到对象可以是不可变的。但 OO 似乎暗示着我的状态/改变。
虽然对于大多数主流 OO 语言都是如此,但 OO 语言没有理由需要具有可变状态。
如果一种语言具有对象、方法、虚拟继承和临时多态性,那么它就是一种面向对象的语言——无论它是否也具有可变状态。
两个概念正交意味着它们可以在任何给定的表现形式中以任何程度独立地实现。例如,考虑到音乐,您可以根据它的谐波程度和节奏程度对音乐作品进行分类。“和声”和“节奏”这两个概念在某种意义上是正交的,即有和声和有节奏的片段、不和谐和无节奏的片段,但也有不和谐和有节奏的片段以及和声和无节奏的片段。
应用于原始问题,这意味着有纯粹的功能性、非面向对象的编程语言,如 Haskell、纯粹的面向对象、“非功能性”的语言,如 Eiffel,但也有既不是 C 语言,也不是语言两者都如Scala。
简单来说,Scala 面向对象意味着您可以定义封装数据的数据结构(“类”和“特征”),并使用操作这些数据的方法,保证这些结构(“对象”)的实例总是在一个已定义状态(对象在其类中的约定)。
另一方面,Scala 作为一种函数式语言意味着它更喜欢不可变而不是可变状态,并且函数是第一类对象,它可以像任何其他对象一样用作其他函数的局部变量、字段或参数。除此之外,几乎 Scala 中的每条语句都有一个值,这鼓励您使用函数式编程风格。
Scala 中面向对象编程和函数式编程的正交性还意味着您作为程序员可以自由选择这两个概念的任何组合,您认为适合您的目的。您可以以纯粹的命令式风格编写程序,仅使用可变对象而不将函数用作对象,另一方面,您也可以在 Scala 中编写纯函数式程序,而不使用其任何面向对象的特性。
Scala 并不要求您使用一种风格或另一种风格。它使您可以选择两全其美的方法来解决您的问题。
像所有分类一样,将编程语言划分为函数式、面向对象、过程式等是虚构的。但是我们确实需要分类,在编程语言中,我们根据一组语言特征和使用该语言的人的哲学方法进行分类(后者受前者的影响)。
因此,有时“面向对象”语言可以成功采用“函数式”编程语言的特性和理念,反之亦然。但肯定不是所有的编程语言特性和理念都是兼容的。
例如,像 OCaml 这样的函数式语言通过词法作用域和闭包来完成封装,而面向对象的语言则使用公共/私有访问修饰符。这些机制本身并不是不兼容的,但它们是多余的,并且像 F#(一种主要是功能性语言,旨在与明确面向对象的 .NET 库和语言堆栈和谐相处)这样的语言必须不遗余力地弥合差距。
作为另一个例子,OCaml 使用面向对象的结构类型系统,而大多数面向对象的语言使用名义类型系统。这些几乎是不兼容的,并且有趣地代表了面向对象语言领域内的不兼容。
对象的想法可以以不可变的方式实现。一个例子是 Abadi 和 Cardelli 所著的“ A Theory of Objects ”一书,该书旨在形式化这些想法,并且首先赋予对象不可变的语义,因为这使得面向对象程序的推理变得更简单。
在这种情况下,传统上会就地修改对象的方法会返回一个新对象,而前一个对象仍然存在。
您可以将函数实现为对象,将对象实现为函数的集合,因此这两个概念之间显然存在某种关系。
FP 尽可能鼓励不变性和纯洁性
您在谈论纯函数式编程。
而 OO 似乎是为状态和突变而构建的
不要求对象是可变的。我会说对象和突变是正交的概念。例如,OCaml 编程语言为纯功能对象更新提供了语法。
像 Scala 这样的语言可以很容易地同时进行 OO 和 FP
并不真地。缺乏尾调用优化意味着大多数惯用的纯函数式代码会在 Scala 中堆栈溢出,因为它会泄漏堆栈帧。例如,连续传球风格(CPS) 以及Bruce McAdam的论文That about wraps it up中描述的所有技术。没有简单的方法可以解决这个问题,因为 JVM 本身无法进行尾调用优化。
关于纯函数式编程和面向对象编程的正交性,我想说它们至少接近正交性,因为纯函数式编程只处理小程序(例如高阶函数),而面向对象编程处理大程序- 程序的规模结构。这就是为什么函数式编程语言通常为大规模结构化提供一些其他机制,例如标准 ML 和 OCaml 的高阶模块系统,或 Common Lisp 的 CLOS 或 Haskell 的类型类。
帮助我理解 FP 和 OO 之间关系的一件事是 SICP 书,尤其是“函数式程序的模块化和对象的模块化”一节前三章,让人大开眼界。
我刚刚找到了关于 OOP 和 FP 正交性的精彩解释。
基本思路如下。想象一下,我们正在使用数学表达式的 AST。所以我们有不同类型的名词(常数、加法、乘法)和不同的动词(eval、toString)。
比方说,表达式是(1 + 2) * 3
。那么AST将是:
乘法 / \ 加法 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 |
+---------------+---------------------+-------------------------------------+
“正交性”来自于这样一个事实,而不是在OOP中,我们将按行来实现这个表:我们将把每个名词表示为一个类,它必须实现每个方法。
另一方面,在FP中,我们将按列实现这个表——我们将为每个动词编写一个函数,这个函数将对不同类型的参数做出不同的反应(可能使用模式匹配)。
正交。这听起来不错。如果你受过教育,你可以把它绑起来假装。它有点像范式。
这一切都取决于你在哪个圈子里旅行,以及每种类型的编程技术会给你带来什么。我已经阅读了一些关于 SS 的帖子,大多数来自函数式编程语言的人通常坚持这样一个事实,即你只能使用函数式编程,而其他任何事情都违背了思维和思维定势。
面向对象的编程主要是关于捕获状态并保持此状态尽可能本地化,以免受到任何不属于您管理状态的对象的影响。另一方面,函数式编程从不同的角度看待状态问题,并试图将状态与系统分离,并将其简化为函数。是的,您可以在代码中使用这两种技术,但它们都从不同的角度看待软件的设计。
函数式编程技术引起了极大的兴趣,主要是因为在处理多核芯片和并行编程时需要对状态进行管理。目前看来,函数式编程在处理这个问题上确实占了上风,但是您可以使用 Objects 来实现相同的效果。你只是对问题的看法不同。不要挠头,尽量摆脱状态,而是查看设计中的对象,看看如何使用设计模式、CRC 和对象分析。对象确实进入其中,而函数式编程更加困难的地方在于分析现实世界并将其映射到可理解的计算机化系统。例如,在 OO 中,个人对象将是状态的封装,其中包含作用于个人状态的方法。在函数式编程中,一个人将被分解为数据部分和作用于个人数据的函数,附加条件是数据应该创建一次且只创建一次并且是不可变的。
我必须承认,虽然我来自 OO 背景,但在大多数 OO 语言中处理多核芯片时,我走的是函数式路线,主要是通过核心编程设计结构(例如线程和委托)并传递伪数据对象。这使我质疑 OO 编程技术,因为它似乎不能很好地映射到这种线程化设计。