函数式编程是面向对象的超集吗?


26

我做的功能越多,我越觉得它会增加一个额外的抽象层,就像洋葱的层如何一样-都包含了先前的层。

我不知道这是否是真的,所以要脱离我多年使用的OOP原则,谁能解释功能如何准确地描述其中的任何一个:封装,抽象,继承,多态性

我想我们都可以说,是的,它具有通过元组进行封装,或者从技术上讲元组是否可以算作“函数式编程”,还是它们只是该语言的实用程序?

我知道Haskell可以满足“接口”要求,但是再次不确定它的方法是否具有功能性?我猜想函子具有数学基础的事实,您可以说函子是对功能的期望而定的,也许吧?

请详细说明您认为功能是否满足OOP的四项原则。

编辑:我了解功能范式和面向对象范式之间的区别就很好,并且意识到现在有很多多范式语言可以同时做到这两种。我真的只是在寻找关于完全fp(例如纯粹主义者,例如haskell)如何完成所列4项中的任何一项的定义,或者为什么它不能做到其中任何一项的定义。即“可以使用闭包来封装”(或者如果我对此信念有误,请说明原因)。


7
这四个原则不会“使” OOP成为现实。OOP只是通过使用类,类hiearchy及其实例来“解决”这些问题。但是我也想要一个答案,如果有方法可以实现函数式编程。
欣快的2012年

2
@Euphoric根据定义,它确实会产生 OOP。
康拉德·鲁道夫

2
@KonradRudolph我知道很多人认为这些东西以及它们带来的好处是OOP的独特属性。假设“多态性”意味着“子类型多态性”,那么我可以将后两者作为OOP不可或缺的一部分。但是我还没有遇到有用的封装和抽象定义,它明确地排除了非OOP方法。即使在Haskell中,您也可以隐藏详细信息并限制对它们的访问。Haskell还具有临时多态性,只是没有亚型多态性-问题是,“亚型”位重要吗?

1
@KonradRudolph但这并不能让人接受。如果有的话,那就是加紧努力并给予那些散布它的人重新考虑的动力。

1
抽象是任何编程所固有的,至少是原始机器代码之外的任何编程。封装早在OOP之前就已经存在了,它是函数编程所固有的。功能语言不需要包含用于继承或多态的显式语法。我想这等于“不”。
斯德纳姆

Answers:


44

函数式编程不在OOP之上。这是一个完全不同的范例。 可以以功能样式(完全出于此目的而编写F#)进行OOP,在频谱的另一端,您可以看到Haskell之类的东西,它明确地拒绝了面向对象的原理。

您可以使用任何高级语言来进行封装和抽象,以支持模块和功能。OO提供了特殊的封装机制,但这不是OO固有的。OO的重点是您提到的第二对:继承和多态。这个概念的正式名称是Liskov替换,如果没有面向对象编程的语言级别支持,您将无法获得它。(是的,在某些情况下可以伪造它,但是您将失去很多OO带给桌面的优势。)

函数式编程并不专注于Liskov替代。它着重于提高抽象水平,并最小化可变状态和带有“副作用”的例程的使用,这是功能程序员喜欢用来使实际执行某些操作(而不是简单地计算某些操作)的例程的声音害怕。但是同样,它们是完全独立的范例,可以一起使用,也可以一起使用,这取决于程序员的语言和技能。


1
好吧,继承(在那些极少数情况下需要继承)是可以通过组合实现的,并且比类型级继承更干净。多态是自然的,尤其是在存在多态类型的情况下。但是我当然同意FP与OOP及其原则无关。
SK-logic

始终可以伪造它-您可以用您选择的任何语言实现对象。我同意其他一切:)
艾略特·鲍尔

5
我认为“副作用”一词不是由功能程序员创造的(或主要使用的)。
sepp2k 2012年

4
@ sepp2k:他并不是说他们发明了这个名词,只是说他们用与通常用来指代拒绝离开草坪的孩子大致相同的语气来操纵它。
亚伦诺特

16
@Aaronaught困扰我的不是我草坪上的孩子,而是他们血腥的副作用!如果他们不再在我的草坪上变乱,我根本不会介意的。
吉米·霍法

10

我发现以下直觉对于比较OOP和FP非常有用。

与其将FP视为OOP的超集,不如将OOP和FP视为查看具有以下相似基础计算模型的两种替代方法:

  1. 一些执行的操作,
  2. 该操作的一些输入参数,
  3. 一些可能影响操作定义的固定数据/参数,
  4. 一些结果值,以及
  5. 可能会有副作用。

在OOP中,这是由

  1. 一种执行的方法
  2. 方法的输入参数,
  3. 调用方法的对象,其中包含一些成员变量形式的本地数据,
  4. 方法的返回值(可能为空),
  5. 该方法的副作用。

在FP中,这是由

  1. 被执行的闭包,
  2. 闭包的输入参数,
  3. 捕获的闭包变量,
  4. 闭包的返回值,
  5. 封闭可能产生的副作用(在像Haskell这样的纯语言中,这是以非常受控的方式发生的)。

通过这种解释,可以将对象视为闭包(其方法)的集合,这些闭包均捕获相同的非局部变量(该对象的成员变量是集合中所有闭包所共有的)。这种观点还受到以下事实的支持:在面向对象的语言中,闭包通常仅使用一种方法建模为对象。

我认为不同的观点源于以下事实:面向对象的视图以对象(数据)为中心,而功能视图的以函数/闭包(操作)为中心。


8

这取决于您要求谁定义OOP。问五个人,您可能会得到六个定义。维基百科说

在对象背后寻找共识定义或理论的尝试并未证明非常成功

因此,每当有人给出非常明确的答案时,都应加盐。

也就是说,有一个很好的论据,是的,FP OOP作为范例的超集。特别是,艾伦·凯(Alan Kay)对“面向对象程序设计”一词的定义与该概念并不矛盾(但克里斯汀·尼加德的观点却与此相反)。凯真正关心的只是一切都是对象,而逻辑是通过在对象之间传递消息来实现的。

对于您的问题,也许更有趣的是,可以根据函数返回的函数和闭包(同时充当类和构造函数)来考虑类和对象。这非常接近基于原型的编程,实际上JavaScript允许这样做。

var cls = function (x) {
    this.y = x;
    this.fun = function () { alert(this.y); };
    return this;
};

var inst = new cls(42);
inst.fun();

(当然,JavaScript允许更改纯函数式编程中非法的值,但在严格的OOP定义中也不是必需的。)

但是,更重要的问题是:这是对OOP 的有意义的分类吗?将其视为功能编程的子集是否有帮助?我认为在大多数情况下不是。


1
我认为在思考转换范例时应该在哪里划界时,这可能是有意义的。如果说无法在fp中实现子类型多态性,那么我将不会再尝试使用fp对其进行建模。如果可能的话,当我在fp空间中大量工作,但又希望在一些利基空间中进行亚型多态性时,我可能会花一些时间来实现一个好的方法(尽管可能不是一个好方法)。显然,如果大多数系统都适合它,那么使用OOP会更好。
Jimmy Hoffa 2012年

6

像OO这样的FP并不是一个明确定义的术语。有些学校有不同的定义,有时是相互矛盾的。如果您采用它们的共同点,那么您可以:

  • 函数式编程是使用一流的函数进行编程

  • OO编程是将包含多态性与至少一种受限形式的动态解析重载组合在一起进行编程。(附带说明:在OO圈子中,多态性通常被认为是包含多态性,而FP学派则通常是指参数多态性。)

其他所有内容都存在于其他地方,或者在某些情况下不存在。

FP和OO是两个抽象构建工具。他们每个人都有自己的优点和缺点(例如,他们在表达问题上有不同的首选扩展方向),但从本质上讲,没有一个比另一个更强大。您可以在FP内核上构建OO系统(CLOS是这样的系统之一)。您可以使用OO框架来获取一流的函数(例如,参见lambda函数在C ++ 11中的定义方式)。


我认为您的意思是“一等函数”而不是“一阶函数”。
dan_waterworth 2012年

错误... C ++ 11 lambda几乎不是一流的函数:每个lambda都有其自己的临时类型(出于所有实际目的,是一个匿名结构),与本机函数指针类型不兼容。并且std::function,可以将函数指针和lambda都分配给的,显然是通用的,而不是面向对象的。这不足为奇,因为面向对象的有限多态性品牌(子类型多态性)严格地不如参数多态性强大(甚至是Hindley-Milner,更不用说完整的System F-omega)了。
pyon

我对纯函数式语言没有很多经验,但是如果您可以在闭包中定义单静态方法类并将它们传递给不同的上下文,我会说(至少很尴尬)功能样式选项。在大多数语言中,有很多方法可以解决严格的参数问题。
Erik Reppen

2

没有; OOP可以看作是过程编程的一个超集,从根本上不同于功能范式,因为它在实例字段中表示状态。在函数范式中,变量是应用于常数数据以获得所需结果的函数。

实际上,您可以考虑对OOP进行功能编程。如果您使所有类都不可变,那么您可能会认为您具有某种功能编程。


3
不可变的类不会产生高阶函数,列表推导或闭包。Fp不是子集。
Jimmy Hoffa 2012年

1
@Jimmy Hoffa:您可以通过创建一个类来轻松地模拟一个更高阶的oreder函数,该类具有一个方法,该方法可以接受一个或多个相似类型的对象,并且还返回该相似类型的对象(具有方法且没有字段的类型) 。列表理解与编程语言无关,不是范式(Smalltalk支持并且是OOP)。闭包存在于C#中,也将插入到Java中。
m3th0dman 2012年

是的,C#具有闭包,但这是因为它是多范式,闭包是与其他fp片段一起添加到C#中的(我对此深表感谢),但是它们在oop语言中的存在并不能使它们成为oop。关于高阶函数的好处是,将方法封装在类中确实允许相同的行为。
吉米·霍法

2
是的,但是如果使用闭包来更改状态,您是否仍将在功能范例中进行编程?关键是-功能范式是关于缺乏状态,而不是高阶函数,递归或闭包。
m3th0dman'9

关于fp定义的有趣想法。感谢您分享您的观点,我将不得不对此进行更多考虑。
Jimmy Hoffa 2012年

2

回答:

Wikipedia上有一篇有关函数式编程的出色文章 ,其中包含您要求的一些示例。@Konrad Rudolph已经提供了有关OOP文章的链接。

我不认为一个范例是另一个范例的超集。它们是编程的不同观点,某些问题可以从一个角度更好地解决,而另一些问题则可以从另一个角度更好地解决。

FP和OOP的所有实现都会使您的问题更加复杂。每种语言都有自己的怪癖,与您对问题的任何良好答案都有关。

切线扰动越来越多:

我喜欢这样的想法:像Scala这样的语言试图为您提供两全其美的体验。我担心这也会给您带来两个世界的麻烦。

Java是一种OO语言,但是版本7添加了“尝试资源”功能,该功能可用于模仿一种封闭方式。在这里,它模仿在另一个函数的中间更新局部变量“ a”,而不使该变量对该函数可见。在这种情况下,另一个函数的前半部分是ClosureTry()构造函数,后半部分是close()方法。

public class ClosureTry implements AutoCloseable {

    public static void main(String[] args) {
        int a = 1;
        try(ClosureTry ct = new ClosureTry()) {
            System.out.println("Middle Stuff...");
            a = 2;
        }
        System.out.println("a: " + a);
    }

    public ClosureTry() {
        System.out.println("Start Stuff Goes Here...");
    }

    /** Interface throws exception, but we don't have to. */
    public void close() {
        System.out.println("End Stuff Goes Here...");
    }
}

输出:

Start Stuff Goes Here...
Middle Stuff...
End Stuff Goes Here...
a: 2

这对于打开流,写入流并可靠地关闭流的预期目的很有用,或者用于简单地将两个函数配对,以使您在它们之间做完一些工作后就不会忘记调用第二个函数。当然,它是如此新奇且不寻常,以至于另一个程序员可能在没有意识到自己破坏了某些东西的情况下删除了try块,因此它目前是一种反模式,但有趣的是它可以做到。

您可以使用大多数命令式语言将任何循环表示为递归。可以使对象和变量不变。可以编写过程以最大程度地减少副作用(尽管我认为在计算机上不可能实现真正的功能-执行所花费的时间以及所消耗的处理器/磁​​盘/系统资源是不可避免的副作用)。某些功能语言也可以做很多(即使不是全部)面向对象的操作。它们不必相互排斥,尽管某些语言具有阻止某些模式(如可变字段)的限制(例如不允许对变量进行任何更新)。

对我而言,面向对象编程最有用的部分是数据隐藏(封装),将足够多的相似对象视为相同(多态),并收集数据和对这些数据一起操作的方法(对象/类)。继承可能是OOP的旗舰,但对我而言,它是最不重要且使用最少的部分。

函数式编程中最有用的部分是不变性(令牌/值而不是变量),函数(无副作用)和闭包。

我不认为它是面向对象的,但是我不得不说,计算机科学中最有用的事情之一是能够声明一个接口,然后具有各种功能和数据来实现该接口。我还喜欢处理一些可变的数据,所以我想我对完全使用函数式语言并不完全满意,即使我试图限制所有程序设计中的可变性和副作用。

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.