13

我需要帮助,让我的头脑围绕当前的OOP状态概念和它在Haskell或Clojure等函数式语言中的做法之间的区别。函数式编程:状态与再调用

要使用一个陈旧的例子,假设我们正在处理简化的银行帐户对象/结构体/任何。在OOP语言中,我会有一些类持有对BankAccount的引用,这些BankAccount将具有诸如利率之类的实例变量,以及像setInterestRate()这样的方法,它们会更改对象的状态并且通常不会返回任何内容。在说Clojure时,我会有一个银行帐户结构(一个荣耀的散列表),以及一些特殊函数,它们需要一个银行帐户参数和其他信息,并返回一个新的结构。因此,我不用改变原始对象的状态,而是通过所需的修改返回新的对象。

那么...我该怎么处理它呢?覆盖引用旧银行帐户的任何变量?如果是这样,这是否比改变状态的面向对象方法有优势?最后,在这两种情况下,似乎都有一个变量引用具有必要更改的对象。尽管我受到了阻碍,但对于发生的事情我只有一个模糊的概念。

我希望这是有道理的,谢谢你的帮助!

+1

此视频是关于此主题的“必备手表”:http://www.infoq.com/presentations/Are-We-There-Yet-Rich-Hickey – mikera 2012-01-06 03:47:49

回答

11

在纯粹的功能风格中,你永远不会覆盖任何变量。

类比将是物理学中的时空。如果你认为世界是三维的,那么物体就没有固定位置 - 它们会随着时间的推移而移动。为了使数学对物理世界产生影响,我们因此增加了时间维度并考虑了特定时间的各种性质的值。在这样做的过程中,我们将研究的对象变为常量。同样,在编程中,通过使用不可变的值来处理概念上的简单性。在现实世界中具有身份的对象可以被建模为一系列不可变的值(对象的状态在不断增加),而不是作为单一值发生变化。

当然,如何将值序列与“对象标识”相关联的细节可能有点多毛。 Haskell有Monads让你模拟状态。 Functional Reactive Programming是一个更纯粹的功能更新在世界上建模对象的文字尝试,我认为这是一个非常有前途的编程方向。

我会注意到,与Haskell不同,Clojure不是纯粹的,您可以按照您的建议更新变量。如果您只是在较高层次上更新一些变量,那么您仍然可能会享受到函数式编程的许多简单概念优点。

4

那么......我该怎么处理它呢?覆盖引用旧银行帐户的任何变量?

如果是这样,这是否有过状态改变OOP方法的优势?

假设您在该结构上执行的任何操作的计算需要很长时间,并且中途发生了一些事情,并且需要恢复到原始结构或计算引发错误。根据你给我的OO的解释(使用一个引用,因为你可以有一个不变的OO语言)数据可能会被破坏 - 除非有足够的信息来自失败的函数调用,否则它是未知的,并且让我们建议它失败厉害。在功能性方法中,您肯定知道您的原始数据结构是正确的 - 因为您最初制作了副本。

在多线程应用程序中扩展此场景。我们可以确保没有其他人使用我们的数据结构,因为我们都有自己的版本。

此外,我们可以使用我们正在复制的其他结构中的数据节省空间。一个典型的例子是当一个元素添加到列表头部时。如果我们有一个指向第二个元素的指针和一个指向第一个元素的指针,那么我们可以只用第一个元素的大小引用这两个列表(见下文)。没有不变性,我们不能保证这一点。

 b__ 
      | 
a -> [6|] -+-> [5|] -> [4|] -> [3|] -> [2|] -> [1|x] 
1

看看Haskell中,这是一个纯粹的功能性语言,它没有重新分配无论如何,还有没有其他的副作用:为了做IO,在IO monad构建它实际上取代了真实世界与一个新的世界实例,例如,在控制台中显示新的文本。

8

假设在面向对象的世界里,你有一个循环,并且一次又一次地修改这些银行账户以响应请求。假设您有一个完整的账户组合,而且这些账户都有类型组合。然后在Haskell你会写一个纯函数

updatePortfolio :: Request -> Portfolio -> Portfolio 

而且你的主循环可能会从标准输入读取请求,并保持你的投资组合最新的。 (这个例子是没有多大用处,除非你可以写组合为好,但它是简单的。)

readRequest :: IO Request -- an action that, when performed, reads a Request with side effects 

main :: Portfolio -> IO() -- a completely useless program that updates a Portfolio in response to a stream of Requests 

main portfolio = do req <- readRequest 
        main (updatePortfolio req) 

,现在我希望你看看发生了什么,以你的可变状态:在一个典型的功能性程序,状态更改作为参数传递给函数。当状态改变时,你进行一个新的函数调用。该调用处于尾部位置(您可以查找'正确的尾部调用'),因此它不会使用任何其他资源,事实上,当编译器生成汇编代码时,它会生成一个循环,并且它将保持指向不断变化的投资组合。

这是一个非常玩具的例子,但我希望它给你一点功能语言的味道。