面向对象的编程在纯粹的函数式编程环境中?

在函数式编程(FP)环境中使用面向对象编程(OOP)有什么优势?

我已经使用F#一段时间了,我注意到我的函数越是没有状态,我就越需要将它们作为对象的方法。 特别是,依靠types推断使它们在尽可能多的情况下可用是有利的。

这并不排除需要一些与OOP正交的forms的命名空间。 也不鼓励使用数据结构。 实际上,FP语言的实际使用在很大程度上依赖于数据结构。 如果你看看F Sharp Programming / Advanced Data Structures中实现的F#栈,你会发现它不是面向对象的。

在我看来,面向对象与处理对象状态的方法主要是为了改变对象有很大关系。 在一个纯粹的FP环境中,这是不需要也不需要的。

一个实际的原因可能是能够与OOP代码交互,就像F#与.NET一样 。 除此之外,有没有什么原因? Haskell世界的经验是什么?编程更纯粹的FP?

我会很感激任何关于这个问题的论文或反事实的例子。

你看到的断开连接不是FP与OOP。 它主要是关于不变性和mathforms主义与可变性和非正式的方法。

首先,让我们放弃可变性问题:你可以让FP具有可变性,OOP具有不变性。 即使function更强大,Haskell也可以让你用所有你想要的可变数据来玩,你只需要明确什么是可变的,以及事情发生的顺序。 和效率问题放在一边,几乎任何可变对象都可以构造并返回一个新的“更新”实例,而不是改变它自己的内部状态。

这里更大的问题是mathforms主义,特别是在很less使用lambda微积分的语言中大量使用代数数据types。 你已经用Haskell和F#标记了这个,但意识到这只是函数式编程的一半。 与ML风格的语言相比,Lisp家族有一个非常不同的,更自由的angular色。 目前广泛使用的大多数面向对象系统在本质上都是非正式的 – forms化确实存在于面向对象中,但是它们并没有明确地以FPforms化方式用ML风格语言来调用。

如果你消除了forms化的不匹配,许多明显的冲突就消失了。 想要在Lisp之上构build一个灵活的,dynamic的,特殊的OO系统? 来吧,它会工作得很好。 想要添加一个正式的,不可变的OO系统到ML风格的语言? 没问题,只是不要期望它能很好地与.NET或Java一起玩。


现在,你可能想知道,面向对象的forms化是什么? 好吧,这里有一句妙语:在很多方面,它比ML风格的FP更加以function为中心! 我将参考我最喜欢的论文之一,看起来似乎是关键区别:像ML风格语言中的代数数据types这样的结构化数据提供了数据的具体表示和定义数据的能力; 对象提供了对行为的黑盒抽象以及轻松replace组件的能力。

这里有一个双重性,比FP和OOP更深层次:它与一些编程语言理论家称之为expression式问题密切相关:用具体的数据,你可以很容易地添加新的操作,但是改变数据的结构更多难。 有了对象,你可以很容易地添加新的数据(例如,新的子类),但添加新的操作是困难的(想想为具有许多后代的基类添加一个新的抽象方法)。

我之所以说OOP更加以function为中心是因为function本身就是一种行为抽象的forms。 事实上,你可以用Haskell这样的东西来模拟OO风格的结构,通过使用logging来保存一堆函数作为对象,让loggingtypes成为一个“接口”或“抽象基类”,并且具有创buildlogging的函数类构造函数。 所以从这个意义上来说,面向对象的语言使用更高阶的函数,这比Haskell所说的要多得多。

对于像Haskell这样的devisetypes的例子来说,它可以很好的使用Haskell,阅读graphics-drawingcombinators包的源代码,特别是使用包含函数的不透明loggingtypes的方式,行为。


编辑:我忘了上面提到的一些最后的东西。

如果OO确实广泛使用高级函数,那么它可能首先看起来应该很自然地适合于像Haskell这样的函数式语言。 不幸的是,情况并非如此。 确实,我描述它们的对象 (参见LtU链接中提到的论文)非常合适。 事实上,结果是比大多数OO语言更纯粹的OO风格,因为“私人成员”由用于构造“对象”的闭包所隐藏的值来表示,并且除了一个特定的实例本身之外对于其他任何东西都是不可访问的。 你并没有比这更私密!

在Haskell中不能很好地工作的是子types 。 而且,虽然我认为inheritance和子types在OO语言中经常被滥用,但是某种forms的子types对于以灵活的方式组合对象是非常有用的。 Haskell缺乏子types的内在概念,而手卷replace往往是非常笨拙的。

顺便说一下,大多数带有静态types系统的面向对象语言(OO)通过对可replace性过于宽松以及不能为方法签名中的方差提供适当的支持,使得子types的完整散列也是完整的。 事实上,我认为唯一完全没有搞砸的OO语言,至less我知道的是Scala(F#似乎对.NET做出了太多的让步,尽pipe至less我不认为它会产生新的错误)。 但是,我对许多这样的语言的经验有限,所以在这里肯定是错的。

在一个Haskell特定的说明中,它的“types”对于OO程序员来说往往是诱人的,我说:不要去那里。 试图以这种方式实施面向对象将只会以眼泪而告终。 将types类看作是重载函数/运算符的替代,而不是OOP。

至于Haskell,类在这方面用处不大,因为一些OO特性更容易以其他方式实现。

封装或“数据隐藏”通常通过函数closures或存在types来完成,而不是私有成员。 例如,这里是一个数据types的随机数生成器,封装状态。 RNG包含生成值和种子值的方法。 由于“种子”types是封装的,因此只能将其传递给方法。

 数据RNG a其中RNG ::(种子→(a,种子))→种子→RNG a 

参数多态性或“generics编程”的上下文中的dynamic方法分派由types类(不是OO类)提供。 一个类的类就像一个OO类的虚拟方法表。 但是,没有数据隐藏。 类类不像类方法那样“属于”数据types。

 数据坐标= C Int Int

实例Eq坐标其中C ab == C de = a == b && d == e

子types多态或“子类化”的上下文中的dynamic方法调度几乎是使用logging和函数对Haskell中的类模式进行的翻译。

 - 有两个“虚拟方法”的“抽象基类”
数据对象=
  目的
   {draw :: Image  - > IO()
   ,翻译::协调 - >对象
   }

 - “子类构造函数”
 circle center radius = Object draw_circle translate_circle
  哪里
     - “子类方法”
     translate_circle center radius offset = circle(center + offset)radius
     draw_circle center radius image = ...

我认为有几种方法可以理解OOP的含义。 对我来说,不是关于封装可变状态 ,而是关于组织和构造程序。 OOP的这个方面可以和FP的概念一起使用。

我相信在F#中混合这两个概念是非常有用的方法 – 您可以将不可变状态与在该状态下运行的操作相关联。 您将获得标识符的“点”完成function,从C#轻松使用F#代码的function等等,但是仍然可以使您的代码具有完美的function。 例如,你可以写如下的东西:

type GameWorld(characters) = let calculateSomething character = // ... member x.Tick() = let newCharacters = characters |> Seq.map calculateSomething GameWorld(newCharacters) 

一开始,人们通常不会在F#中声明types,只需要编写函数就可以开始编写代码,然后使用代码来使用它们(当您更好地理解域并知道构build代码​​的最佳方式时)。 上面的例子:

  • 仍然是纯粹的function(国家是一个字符的列表,它没有变异)
  • 它是面向对象的 – 唯一不寻常的是所有的方法都返回一个新的“世界”