您看到的断开连接不是FP与OOP。它主要是关于不变性和数学形式主义与可变性和非正式方法。首先,让我们放弃可变性问题:你可以让FP具有可变性,而OOP具有不变性就好。即使功能更强大,Haskell也可以让你用所有你想要的可变数据来玩,你只需要明确什么是可变的,以及事件发生的顺序;和效率问题放在一边,几乎任何可变对象都可以构造并返回一个新的“更新”实例,而不是改变它自己的内部状态。
这里的一个更大的问题是数学形式,特别是在很少使用lambda微积分的语言中大量使用代数数据类型。你已经用Haskell和F#标记了这一点,但意识到这只是函数式编程领域的一半;与ML风格的语言相比,Lisp家族有一个非常不同的,更自由的角色。目前广泛使用的大多数面向对象系统本质上都是非正式的 - 形式化确实存在于面向对象,但它们没有明确地以FP形式主义在ML风格语言中的方式被调用。
如果您删除了形式主义不匹配,许多明显的冲突就会消失。想要在Lisp之上构建一个灵活的,动态的,特殊的OO系统?继续,它会工作得很好。想要将正式的,不可变的OO系统添加到ML风格的语言中?没问题,只是不要指望它能很好地与.NET或Java搭配。
现在,你可能想知道,什么是为 OOP适当的形式主义?好吧,这里有一句妙语:在很多方面,它比ML风格的FP更加以功能为中心!我将回头看one of my favorite papers看起来关键的区别:像ML风格语言中的代数数据类型这样的结构化数据提供了数据的具体表示以及定义数据的能力;对象提供了超过行为的黑盒抽象以及轻松替换组件的功能。
这里有一个比FP和OOP更深层次的二元性:它与一些编程语言理论家称之为the Expression Problem的东西密切相关:使用具体的数据,您可以轻松地添加与它一起工作的新操作,但更改数据的结构比较困难。使用对象可以轻松添加新数据(例如,新的子类),但添加新操作很困难(想想为具有许多后代的基类添加新的抽象方法)。
我说OOP更加以功能为中心的原因是功能本身代表了一种行为抽象的形式。事实上,你可以通过使用记录来持有一堆函数作为对象来模拟OO风格的结构,让记录类型成为一种“接口”或“抽象基类”,并且具有创建记录替换的功能类构造函数。所以从这个意义上来说,面向对象的语言使用的是高阶函数,这比Haskell所说的要多得多。
对于这种类型的设计在Haskell中实际使用非常好的例子,请阅读the graphics-drawingcombinators package的源代码,特别是它使用包含函数的不透明记录类型的方式,行为。
编辑:最后几点东西,我忘了提及以上。
如果OO确实广泛使用高阶函数,它可能首先看起来应该很自然地适合于像Haskell这样的函数式语言。不幸的是,情况并非如此。确实,我所描述的对象(参见LtU链接中提到的论文)非常合适。实际上,结果是比大多数OO语言更纯粹的OO风格,因为“私有成员”由用于构造“对象”的闭包隐藏的值表示,并且除了一个特定的实例本身之外其他任何东西都不可访问。你不会比这更私密!
什么在Haskell中不能很好地工作是子类型。而且,虽然我认为继承和子类型在OO语言中经常被滥用,但是某些形式的子类型对于能够以灵活的方式组合对象非常有用。 Haskell缺乏子类型的固有概念,而手卷替换往往非常笨拙。另外,大多数带有静态类型系统的面向对象语言通过对可替代性过于宽松以及不能为方法签名中的方差提供适当的支持来完成子类型的完整散列。事实上,我认为唯一完全没有搞砸的OO语言,至少我知道的是Scala(F#似乎让.NET有太多让步,尽管至少我不认为它使任何新的错误)。尽管如此,我对许多此类语言的使用经验有限,所以我在这里肯定会出错。
在一个Haskell特有的注释中,它的“类型”对OO程序员来说通常很诱人,我说:不要去那里。试图以这种方式实施OOP只会以泪结束。将类型类看作是重载函数/运算符的替代,而不是OOP。
+1,好答案! – missingfaktor 2010-11-06 18:44:47
感谢您给出了令人印象深刻的答案(和链接)。阅读材料后,我可能会跟进问题。 – 2010-11-07 18:25:09