如何编写一个不会改变状态的应用程序(用函数式语言)?

时间:2009-01-25 08:23:12

标签: functional-programming immutability

我希望有一天能学习函数式编程,但我不明白如何将它用于除简单数学之外的其他任何东西。

例如:一个简单的Web浏览器添加书签功能需要引起某种突变,以便下次用户点击书签时,新书签就会出现在列表中。

6 个答案:

答案 0 :(得分:4)

一个有用的应用程序作为一个整体通常必须改变几件事的状态,但这并不意味着所有或你的函数都需要改变状态才有用。

函数式编程中的

Monads表示输入/输出(I / O)操作和状态变化,而不使用引入副作用的语言特性。

我认为你只是在面向对象方面思考。仅仅因为函数总是在给定相同输入的情况下给出相同的输出,并不意味着它不能接受不同类型的输入(可能是无限的输入可能性)并产生不同类型的输出。

使用函数式编程,您将处理不可变对象而不是可变对象。如果输入了一个对象,则可以创建一个新的修改对象并返回该对象。

看看这个article about MapReduce by Joel on software。它包含一个很好的例子,说明为什么不改变状态的函数可以完全有用。

答案 1 :(得分:4)

您的示例不需要可变状态。对于“添加书签”,可以创建新的书签列表,其具有与旧列表相同的内容但具有一个新条目。这是大多数不可变编程的工作方式;您只需创建一个反映世界新状态的新对象图,而不是对现有对象图进行更改。当一切都是不可变的时,很容易在“旧”和“新”版本之间共享对象图的大部分。

在您的示例中更深层次,假设“浏览器”是具有以下信息的数据结构对象:当前网页,用户主页和书签列表。这可以在内存中表示为看起来像这样的树:

browser = {
 curPage --> "http://current"
 homePage --> "http://home"
 favorites --> { data --> "http://firstFav"
                 next --> { data --> "http://secondFav"
                            next --> null } }
}

现在'AddFavorite'函数将输入这样的数据结构并返回一个新的数据结构

browser = *{
 curPage --> "http://current"
 homePage --> "http://home"
 favorites --> *{ data --> *"http://newFav"*
                 next --> { data --> "http://firstFav"
                            next --> { data --> "http://secondFav"
                                       next --> null } } }*
}*

标有'*'的位是新对象 - 在收藏夹前面有一个新的链表节点,包含一个新字符串,浏览器结构本身是新的(必须是因为它有一个新的'收藏夹' '指针)。

你可以像这样模拟所有'有状态'计算,作为将“前世界”作为输入并将“下一个世界”作为输出返回的函数;这就像Haskell这样的语言中的状态monad的本质。

答案 2 :(得分:3)

即使在像Haskell这样的纯函数式语言中, 也必须操纵状态。这是通过monads完成的。

答案 3 :(得分:2)

如何处理“可变性”的好例子。假设您使用函数式语言实现了一些数据结构,比如说AVL树。在您实现的函数(插入,删除等)以及内部函数(旋转等)中,您实际上并不会改变数据,而是返回变异数据。

底层运行时系统可确保您的程序有效地使用内存(例如,它会执行写入时复制和垃圾回收)。

真正更改世界状态(I / O,GUI)时,在程序的某些部分中,有两种方法。

  • 纯函数式语言,如Haskell,将这些操作封装在monad中(警告:阅读它们时你的头可能会爆炸,但不要太担心)
  • 其他功能语言,如OCaml,允许程序中的可变性和副作用。

答案 4 :(得分:2)

许多其他答案会让你急于使用monads或其他一些奇特的技术来编程使用可变状态。虽然我亲眼听到Haskell 98报告的编辑称Haskell为“世界上最好的命令式语言”,但你并不需要像其他答案所建议的那样多的可变状态。在功能程序中,将状态保持在功能参数

例如,我刚刚编写了一个Haskell程序,该程序决定如何将我的音乐备份到DVD,以便同一张专辑中的歌曲放在同一张DVD上,而每张DVD(除了最后一张)都至少为99.9 %满。 DVD的列表和哪张专辑的列表在哪些DVD上不断变化,但不涉及引用,monad或其他奇特的功能。这些值只是递归函数的参数。

要了解更多示例和解释,请阅读John Hughes的非常好的教程论文Why Functional Programming Matters

答案 5 :(得分:0)

另外,在实践中,许多用函数式编程语言编写的应用程序实际上都使用像(set!)这样的副作用函数来实际改变状态。它在理论上并不纯粹,但肯定能完成工作。

(特别是我正在考虑一种流行的消费者软件,它是用LISP衍生产品编写的,但必须在常量内存中运行,所以像垃圾收集这样的东西不得不走出窗外。)