我已经从OOP的角度开始学习如何编程(我敢肯定,像我们大多数人一样),但是我已经花了很多时间试图学习如何以函数方式解决问题。我对如何使用FP解决计算问题有很好的了解,但是当涉及到更复杂的问题时,我总是发现自己会转向需要可变的对象。例如,如果我正在编写粒子模拟器,则希望更新具有可变位置的粒子“对象”。使用函数式编程技术通常如何解决固有的“状态”问题?
我已经从OOP的角度开始学习如何编程(我敢肯定,像我们大多数人一样),但是我已经花了很多时间试图学习如何以函数方式解决问题。我对如何使用FP解决计算问题有很好的了解,但是当涉及到更复杂的问题时,我总是发现自己会转向需要可变的对象。例如,如果我正在编写粒子模拟器,则希望更新具有可变位置的粒子“对象”。使用函数式编程技术通常如何解决固有的“状态”问题?
Answers:
功能程序可以很好地处理状态,但是需要以不同的方式查看状态。对于您的职位示例,要考虑的一件事是让您的职位是时间的函数,而不是固定值。这对于遵循固定数学路径的粒子效果很好,但是您需要采用不同的策略来处理路径的变化,例如在碰撞之后。
这里的基本策略是创建具有状态并返回新状态的函数。因此,粒子模拟器将是一个将一个Set
粒子作为输入并Set
在一个时间步长之后返回一个新粒子的函数。然后,只需重复调用该函数并将其输入设置为先前的结果即可。
如@KarlBielefeldt所指出的,解决此问题的功能方法是将其视为从先前状态返回新状态。这些函数本身不保存任何信息,因此它们将始终将状态m更新为状态n。
我认为您发现这种效率低下是因为您假设在计算新状态时必须将先前的状态保存在内存中。请注意,从功能语言的角度来看,在编写全新状态还是就地重写旧状态之间的选择是实现细节。
例如,假设我有一个一百万个整数的列表,并且想将十分之一增加一个单位。复制整个列表,将其第十个位置的新编号浪费掉,这是对的;但这只是向语言编译器或解释器描述操作的概念性方式。编译器或解释器可以自由选择第一个列表,而仅覆盖第十个位置。
以这种方式描述操作的好处是,当许多线程想要在不同位置更新同一列表时,编译器可以对此情况进行推理。如果将操作描述为“转到该位置并覆盖找到的内容”,那么负责确保覆盖不会冲突的是程序员,而不是编译器。
综上所述,即使在Haskell中,也有一个State monad可以帮助建模“保持状态”是对问题的更直观解决方案的情况。但也请注意,您发现“ 固有状态(如写入数据库) ”的一些问题具有不可变的解决方案,例如Datomic。除非您了解它是一个概念,不一定是它的实现,否则这可能令人惊讶。
在编写大型和中型大型应用程序时,我经常发现区分应用程序中有状态的部分和无状态的部分很有用。
有状态部分中的类/数据结构存储应用程序的数据,并且此部分中的功能在隐含了解应用程序数据的情况下工作。
无状态部分中的类/数据结构/函数在那里支持应用程序的纯算法方面。他们没有应用程序数据的隐性知识。它们以纯粹的功能性质工作。应用程序的有状态部分可能会由于应用程序的无状态部分中运行功能的副作用而经历状态更改。
最困难的部分是弄清楚将哪些类/函数放入无状态部分,将哪些类/函数放入有状态部分,并有纪律将它们放在单独的文件/库中。