我需要帮助了解我当前的OOP状态概念与Haskell或Clojure等函数式语言的区别。
要使用一个陈腐的例子,假设我们正在处理简化的银行账户对象/结构/任何事情。在OOP语言中,我有一些类持有对BankAccount的引用,BankAccount将具有诸如利率之类的事件的实例变量,以及诸如setInterestRate()之类的方法,其改变对象的状态并且通常不返回任何内容。在说Clojure中,我有一个银行账户结构(一个美化的散列图),以及带有银行账户参数和其他信息的特殊函数,并返回一个新的结构。因此,我现在不再更改原始对象的状态,而是返回一个具有所需修改的新对象。
那么......我该怎么办呢?覆盖引用旧银行帐户的任何变量?如果是这样,那是否比改变状态的OOP方法有优势?最后,在这两种情况下,似乎有一个变量引用具有必要更改的对象。像我一样迟钝,我对发生的事情只有一个模糊的概念。
我希望这是有道理的,谢谢你的帮助!
答案 0 :(得分:11)
在纯函数式中,您永远不会覆盖任何变量。
类比将是物理学中的时空。如果你认为世界是3d,那么物体就没有固定的位置 - 它们随着时间的推移而移动。为了使数学对物理世界产生影响,我们因此添加时间维度并在特定时间考虑各种属性的值。在这样做的过程中,我们将研究对象变为常数。类似地,在编程中,通过使用不可变值有一个概念上的简单性。在现实世界中具有身份的对象可以被建模为一系列不可变值(对象在不断增加的时间的状态),而不是作为单个值的变化。
当然,如何将值序列与“对象标识”相关联的细节可能有点毛茸茸。 Haskell有Monads让你模拟状态。 Functional Reactive Programming是一种更为文字的尝试,用纯粹的功能更新来模拟世界中的对象,我认为这是一个非常有前途的编程方向。
我会注意到,与Haskell不同,Clojure不是纯粹的,你可以按照你的建议更新变量。如果你只是在高级别更新一些变量,你仍然可能会享受函数式编程的许多概念简单优势。
答案 1 :(得分:8)
据推测,在OO世界中,您有一个循环并且正在一次又一次地修改这些银行账户以响应请求。假设您有一整套帐户,这些帐户都有类型投资组合。然后在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)
现在我希望你看到你的可变状态发生了什么:在典型的功能程序中,声明更改作为参数传递给函数。当状态改变时,你进行一个新的函数调用。调用处于尾部位置(你可以查找'正确的尾部调用'),因此它不使用任何额外的资源,事实上当编译器生成汇编代码时它会生成一个循环,它会保持指针指向寄存器中不断变化的投资组合。
这是一个很好的玩具示例,但我希望它能为您提供一些功能性语言的风格。
答案 2 :(得分:4)
所以...我该怎么办呢?覆盖引用旧银行帐户的任何变量?
是
如果是这样,那是否比改变状态的OOP方法有优势?
假设您对该结构执行的任何操作的计算需要很长时间,并且某些事情发生在中途,您需要恢复到原始结构或计算引发错误。通过你向OO呈现的解释(使用引用,因为你可以使用不可变的OO语言)数据可能已损坏 - 除非从失败的函数调用中获得足够的信息,否则我知道它失败了厉害。在功能方法中,您确定原始数据结构是正确的 - 因为您最初制作了副本。
在多线程应用程序中扩展此方案。我们可以确保没有其他人使用我们的数据结构,因为我们都拥有自己的版本。
此外,我们可以使用来自我们复制的其他结构的数据来节省空间。一个典型的例子是在列表的头部添加元素。如果我们有一个指向第二个元素的指针,以及指向第一个元素的指针,我们可以引用只有第一个元素大小的两个列表(见下文)。没有不变性,我们无法保证这一点。
b__
|
a -> [6|] -+-> [5|] -> [4|] -> [3|] -> [2|] -> [1|x]
答案 3 :(得分:1)
看看Haskell,它是一种纯函数式语言 - 它没有任何重新赋值,也没有其他副作用:为了做IO,在 IO monad 构造它实际上用一个新的世界实例替换 RealWorld ,例如,在控制台中显示新文本。