【问题标题】:Functional programming: state vs. reassignment函数式编程:状态与重新分配
【发布时间】:2010-09-26 02:49:19
【问题描述】:

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

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

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

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

【问题讨论】:

标签: language-agnostic oop functional-programming comparison immutability


【解决方案1】:

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

是的

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

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

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

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

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

【讨论】:

    【解决方案2】:

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

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

    当然,如何将值序列与“对象标识”相关联的细节可能有点复杂。 Haskell 有 Monads 可以让你模拟状态。 Functional Reactive Programming 是一个更直接的尝试,它通过纯功能更新对世界上的对象进行建模,我认为这是一个非常有前途的编程方向。

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

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        【解决方案4】:

        大概在 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)
        

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-11-16
          • 2021-10-22
          • 1970-01-01
          • 1970-01-01
          • 2019-05-02
          • 1970-01-01
          • 2012-08-17
          • 1970-01-01
          相关资源
          最近更新 更多