13

我需要帮助来了解我当前的 OOP 状态概念与使用 Haskell 或 Clojure 等函数式语言的方式之间的差异。

举一个陈腐的例子,假设我们正在处理简化的银行账户对象/结构/任何东西。在 OOP 语言中,我将有一些类持有对 BankAccount 的引用,该类将具有用于利率之类的实例变量,以及诸如 setInterestRate() 之类的方法,它们会更改对象的状态并且通常不返回任何内容。比如说 Clojure,我有一个银行账户结构(一个美化的哈希图),以及接受银行账户参数和其他信息并返回一个新结构的特殊函数。因此,我现在没有更改原始对象的状态,而是返回了一个带有所需修改的新对象。

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

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

4

4 回答 4

11

在纯函数式风格中,您永远不会覆盖任何变量。

一个类比是物理学中的时空。如果您将世界视为 3d,那么对象没有固定的位置 - 它们会随着时间移动。为了将数学应用于物理世界,我们因此添加了时间维度并考虑特定时间各种属性的值。在这样做的过程中,我们已经将我们研究的对象变成了常量。类似地,在编程中,使用不可变值可以简化概念。现实世界中具有身份的对象可以建模为一系列不可变值(对象在递增时间的状态),而不是一个变化的值。

当然,如何将值序列与“对象标识”相关联的细节可能有点麻烦。Haskell 有Monads可以让你模拟状态。 函数式反应式编程是一种更直接的尝试,它通过纯函数更新来建模世界上的对象,我认为这是一个非常有前途的编程方向。

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

于 2008-12-09T20:10:23.600 回答
8

大概在 OO 世界中,您有一个循环,并且为了响应请求而一遍又一遍地修改这些银行帐户。假设您有一个完整的账户组合,并且这些账户的类型为 Portfolio。然后在 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)

现在我希望你看看你的可变状态发生了什么:在一个典型的函数式程序中,变化的状态作为参数传递给一个函数。当状态改变时,你会调用一个新的函数。该调用位于尾部位置(您可以查找“正确的尾部调用”),因此它不使用任何额外的资源,事实上,当编译器生成汇编代码时,它会生成一个循环,它会保留指向寄存器中不断变化的投资组合。

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

于 2008-12-10T03:57:40.393 回答
4

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

是的

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

假设您在该结构上执行的任何操作的计算需要很长时间,并且中途发生了一些事情,您需要恢复到原始结构或计算引发错误。根据您向我提供的对 OO 的解释(使用参考,因为您可以拥有不可变的 OO 语言),数据可能已损坏——除非从失败的函数调用中提供足够的信息,否则它是未知的,并建议它失败很糟糕。在函数式方法中,您可以确定您的原始数据结构是正确的——因为您最初制作了一个副本。

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

此外,我们可以通过使用我们从中复制的其他结构中的数据来节省空间。一个经典的例子是在列表的头部添加一个元素。如果我们有一个指向第二个元素的指针和一个指向第一个元素的指针,我们可以仅使用第一个元素的大小来引用这两个列表(见下文)。没有不变性,我们无法保证这一点。

        b__
           |  
a -> [6|] -+-> [5|] -> [4|] -> [3|] -> [2|] -> [1|x]
于 2008-12-09T19:58:18.960 回答
1

看看 Haskell,它是一种纯函数式语言——它没有任何重新赋值,也没有其他副作用:为了进行 IO,在IO monad构造中,它实际上用一个新的实例替换了RealWorld例如,在控制台中显示新文本的世界。

于 2008-12-09T21:29:16.483 回答