在函数式编程中处理状态问题


18

我已经从OOP的角度开始学习如何编程(我敢肯定,像我们大多数人一样),但是我已经花了很多时间试图学习如何以函数方式解决问题。我对如何使用FP解决计算问题有很好的了解,但是当涉及到更复杂的问题时,我总是发现自己会转向需要可变的对象。例如,如果我正在编写粒子模拟器,则希望更新具有可变位置的粒子“对象”。使用函数式编程技术通常如何解决固有的“状态”问题?


4
第一步可能是意识到问题并非天生就具有状态。
Telastyn 2014年

4
有些问题本质上是有状态的,例如写入数据库或绘制gui。以我的粒子模拟器为例,考虑它的另一种方法是什么?每次为了避免状态而更新位置时都返回新粒子对我来说似乎效率低下,这不是现实世界的好模型。
Andrew Martin

4
除了数据库示例外,这些问题并不是固有的状态问题。例如,对于GUI编程,您实际上是使用可变状态作为不良的隐式时间模型;功能性反应式编程使您可以通过提供可以组合的事件流来显式地建模时间,而无需依赖状态。
Tikhon Jelvis,2014年

1
有一个更简单的解决方案:遇到不容易用FP技术建模的问题时,请勿使用函数式编程来解决。 正确的工具来完成工作以及所有这些……
Mason Wheeler

1
@AndrewMartin不是现实世界的好模型吗?物理学中用来模拟现实世界的数学纯​​粹是功能性的。对于一个好的垃圾收集器来说,分配一个对象的成本与碰撞一个指针一样便宜,并且收集时间与活动对象的数量成正比。如果有的话,我敢打赌,函数式编程效率低下的主要原因是使用的缓存效率不高。链接列表和二叉树并不是缓存效率的真正体现。
2015年

Answers:


20

功能程序可以很好地处理状态,但是需要以不同的方式查看状态。对于您的职位示例,要考虑的一件事是让您的职位是时间的函数,而不是固定值。这对于遵循固定数学路径的粒子效果很好,但是您需要采用不同的策略来处理路径的变化,例如在碰撞之后。

这里的基本策略是创建具有状态并返回新状态的函数。因此,粒子模拟器将是一个将一个Set粒子作为输入并Set在一个时间步长之后返回一个新粒子的函数。然后,只需重复调用该函数并将其输入设置为先前的结果即可。


5
+1在FP中具有状态是可以的,只是不能处于可变状态。
jhewlett 2014年

1
感谢您的见解。@logc阻止了我对效率低下的担忧;状态如何转换的技术细节是语言本身应该解决的底层实现问题。我观看了Rich Hickey在视频中向Clojure解释了他的操作方式。
Andrew Martin

1
@jhewlett:更精确地说:FP确实具有状态,甚至是可变状态,但是它们不使用可变变量来表示它。
乔治

9

如@KarlBielefeldt所指出的,解决此问题的功能方法是将其视为从先前状态返回新状态。这些函数本身不保存任何信息,因此它们将始终将状态m更新为状态n

我认为您发现这种效率低下是因为您假设在计算新状态时必须将先前的状态保存在内存中。请注意,从功能语言的角度来看,在编写全新状态还是就地重写旧状态之间的选择是实现细节

例如,假设我有一个一百万个整数的列表,并且想将十分之一增加一个单位。复制整个列表,将其第十个位置的新编号浪费掉,这是对的;但这只是向语言编译器或解释器描述操作的概念性方式。编译器或解释器可以自由选择第一个列表,而仅覆盖第十个位置。

以这种方式描述操作的好处是,当许多线程想要在不同位置更新同一列表时,编译器可以对此情况进行推理。如果将操作描述为“转到该位置并覆盖找到的内容”,那么负责确保覆盖不会冲突的是程序员,而不是编译器。

综上所述,即使在Haskell中,也有一个State monad可以帮助建模“保持状态”是对问题的更直观解决方案的情况。但也请注意,您发现“ 固有状态(如写入数据库) ”的一些问题具有不可变的解决方案,例如Datomic。除非您了解它是一个概念,不一定是它的实现,否则这可能令人惊讶。


4
我认为有关更新大型列表的代码片段具有误导性;我不知道任何编译器会真正为您执行该优化。即使编译器可以做到这一点,也只有在您不保留列表的早期版本的情况下才有可能。在真正的解决方案是使用一个列表数据结构,不需要复制整个事情改变单一的元素。
2015年

@Doval:“即使编译器可以做到这一点,也只有在您不保留列表的先前版本的情况下才有可能。”:这让我想起了Clean中的唯一类型。
Giorgio


3

在编写大型和中型大型应用程序时,我经常发现区分应用程序中有状态的部分和无状态的部分很有用。

有状态部分中的类/数据结构存储应用程序的数据,并且此部分中的功能在隐含了解应用程序数据的情况下工作。

无状态部分中的类/数据结构/函数在那里支持应用程序的纯算法方面。他们没有应用程序数据的隐性知识。它们以纯粹的功能性质工作。应用程序的有状态部分可能会由于应用程序的无状态部分中运行功能的副作用而经历状态更改。

最困难的部分是弄清楚将哪些类/函数放入无状态部分,将哪些类/函数放入有状态部分,并有纪律将它们放在单独的文件/库中。


这如何回答这个问题?(无投票权)
投票

@kravemir,无论您使用OOP还是FP编写应用程序,都必须了解应用程序的状态。
R Sahu
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.