如何从功能上处理外部系统的状态?

时间:2012-04-11 00:37:06

标签: functional-programming referential-transparency architectural-patterns

我最近进入了函数式编程,并且我学会了几种以参考透明的方式处理某些副作用的方法:

  • the State monad用于更新变量的可变状态
  • the IO monad用于I / O,例如从/向控制台读/写
  • FRP用于交互,如图形和输入设备事件

但是现在大多数“真实世界”的应用程序都与外部系统(如Web服务,数据库等)连接,这些系统可以由多个用户同时修改,它们具有状态,长时间运行等操作。所以案例是不像上面那样简单:要么系统询问实体状态还是试图控制它的结果取决于它的状态。此外,交互性也是一个要求:用户可以任意点击一些GUI,也许我们还必须自动对来自系统的变化作出反应。

通过最大限度地发挥纯函数的优势,设计和实现此类应用程序的模式是什么?或者可以用我没想到的方式将上述某些方法应用于这个问题?语言(比如Java或Scala)不能实现100%的纯度,因此我对实践经验支持的实用解决方案感兴趣。

3 个答案:

答案 0 :(得分:3)

我还没有做过很多这样的事情,实际上是以实际方式完成的,所以其他人希望能够做得更多。然而,我在Scala中编写了一个Android应用程序,它有几个要求; UI是交互式的,“状态”全部存储在SQLite数据库中。数据库和UI都需要与Android框架接口,Android框架非常面向Java,因此不适合Scala或函数式编程。

我所做的是采用类似MVC设计的东西,其中模型部分被实现为一组仅支持纯操作的ADT。这具有额外的优势,即模型代码完全独立于Android框架,因此可以在模拟器外部以任何我喜欢的方式进行测试。

这给我留下了控制器(视图是一个非常薄的层,主要是配置,Android的工作方式),以及“从数据库加载模型”和“将模型保存到数据库”。作为Scala,我只是用不纯的代码实现这些部分,这些代码调用纯模型代码来进行实际的数据操作。在Haskell中,这些部分可能完全属于IO monad [1]。

总而言之,这让我能够以纯粹的功能性术语来思考我的问题领域,而不必在与外部系统连接时“违背问题”。数据库层成为数据库模式和我用于数据模型的ADT之间的映射问题;处理这些操作可能失败的事实是控制器(启动数据库操作)的责任,并且不会影响模型。控制器操作在概念上非常简单,例如“按下此按钮时,将当前状态设置为在当前状态调用函数的结果,然后更新显示表”。最后,这个不纯的“胶水”代码比实际的模型代码更多,但我仍然认为以这种方式完成程序是一个胜利,因为我的应用程序的核心是操纵复杂的结构化数据,所以做到这一点是最棘手的一点。其余的写作主要是乏味而不是难以设计。

当你的程序中有大量的计算时(不一定是大量的数据,只是你实际计算它上面的东西),这是有效的。如果程序几乎完全将不同的外部系统粘合在一起,那么你不一定能获得同样多的收益。


[1]记住IO monad不只是用于从控制台读/写。结果受程序外部状态影响的建模操作正是IO monad ;一般情况下,Haskellers会尝试避免将IO monad用于几乎整个程序,如果它不是一直与外部系统交互(或者最好即使它是)。您可以对数据进行纯计算以响应来自“外部”的事件,甚至可以执行 需要执行以响应外部事件的IO操作,如果这很复杂的话。 / p>

答案 1 :(得分:2)

  

但是现在大多数“真实世界”的应用程序都与外部系统(如Web服务,数据库等)连接,这些系统可以由多个用户同时修改,它们具有状态,长时间运行等操作。所以案例是不像上面那样简单:要么系统询问实体状态还是试图控制它的结果取决于它的状态。此外,交互性也是一个要求:用户可以任意点击一些GUI,也许我们还必须自动对来自系统的变化作出反应。

交互式地,同时编辑共享状态只是状态monad的另一个例子。你可能会使用镜头或其他一些抽象来编写数据结构的编辑,但是你所拥有的只是一个共享的全局状态。

如果需要机器级并发支持,可以使用并发结构(如STM var或MVar)来解决并发编辑中的冲突。这意味着您将进入STM或IO monad。

对于Haskell软件包,在Hackage上为这些类型的作业设计了许多monadic环境的例子。

答案 2 :(得分:0)

Asynchronous streams a.k.a。iteratees似乎是一个有用且相关的抽象,似乎值得进一步探索它们......