纯函数式编程中的竞态条件

Race conditions in pure functional programming

我遇到过这样的说法:

"Programming in a functional style makes the state presented to your code explicit, which makes it much easier to reason about, and, in a completely pure system, makes thread race conditions impossible."

我看到了这个观点,但是在实际问题中如何实现呢?

例如:

有一个功能程序有两个功能:

def getMoney(actMoney: Integer, moneyToGet: Integer): Integer 
    = actMoney - moneyToGet

def putMoney(actMoney: Integer, moneyToPut: Integer): Integer  
    = actMoney + moneyToPut

然后,我真的很想为给定的帐户定义函数 getActualMoney 和 saveActualMoney,但我不能,它们不是纯粹的。那是因为我从某个内存中获取给定帐户的资金,并将给定帐户的资金保存到某个内存中(有状态)。

def getActualMoney(accountNo: String): Integer = {...}

def saveActualMoney(accountNo: String, actMoney: Integer): Unit = {...}

所以我必须从 "outside" 获得我当前的钱。假设我的程序以这种方式运行。现在我有两个同时的请求,第一个:得到一些钱,第二个是为同一个帐户存入一些钱。当然我会得到两个不同的结果。所以存在竞争条件。

我明白,我应该在这个帐户上进行交易 "outside" 编程代码。所以,这种情况是不应该发生的。为了更好的并发性,函数应该是这样的:

def getMoney(
        acountNo: String, 
        actMoney: Integer, 
        moneyToGet: Integer): (String, Integer) 
    = (acountNo, actMoney - moneyToGet)

def putMoney(
        acountNo: String,
        actMoney: Integer, 
        moneyToPut: Integer): (String, Integer) 
    = (acountNo, actMoney + moneyToPut)        

这是怎么回事?值得做吗?

虽然引用在技术上是正确的,因为你不能在纯函数代码中有竞争条件,你确实需要有副作用才能让程序做任何有用的事情,而不是计算一个值,等等这有点虚伪,但同时它确实说明了一个有用的区别。

最小化副作用和最大化纯函数代码的风格编程将减少可能发生竞争条件的代码区域到那些你知道你明确使用副作用的地方,这通常是纯函数式编程倡导者吹捧的好处。

对于你的例子:如果有钱的账户只是模拟,只存在于你的程序中,那么很容易将它们保持为纯函数,通过对账户进行每个操作 return new 具有更新值的帐户对象。如果你这样做,你通常会获得纯函数代码的好处:能够通过使用 "equational reasoning" 来推理引用透明代码,你可以假设对函数的任何调用都等同于函数 returns 给定它传递的参数的值,无论您以什么顺序调用它多少次。 (这是为了回答你 "is it worth doing?" 的问题)

但是如果你谈论的是一个系统,比如说,在外部数据库中存储账户和余额,那么你肯定需要副作用,你将不得不关注操作顺序和潜在的竞争条件就像您使用任何其他语言一样。但是,通过隔离副作用,您可以更加确定 do 必须关心此类并发问题的位置,并且还可以使用更高级别的并发抽象,例如 MVars 、IORefs 或 STM(在 Haskell 和 Scala 中可用),它们鼓励对不纯数据使用纯操作——保持事物的隔离性和可组合性。