我希望有一天能学习函数式编程,但我不明白如何将它用于除简单数学之外的其他任何东西。
例如:一个简单的Web浏览器添加书签功能需要引起某种突变,以便下次用户点击书签时,新书签就会出现在列表中。
答案 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)时,在程序的某些部分中,有两种方法。
答案 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衍生产品编写的,但必须在常量内存中运行,所以像垃圾收集这样的东西不得不走出窗外。)