我遇到过这样的陈述:
“以函数式编程使得代码显式呈现在状态中,这使得更容易推理,并且在完全纯粹的系统中,使线程竞争条件变得不可能。”
我看到了这一观点,但我怎样才能在现实问题中实现这一目标?
例如:
有一个功能程序有两个功能:
def getMoney(actMoney: Integer, moneyToGet: Integer): Integer
= actMoney - moneyToGet
def putMoney(actMoney: Integer, moneyToPut: Integer): Integer
= actMoney + moneyToPut
然后,我真的想为给定的帐户定义函数getActualMoney和saveActualMoney,但我不能,它们不是纯粹的。那是因为我从一些内存中获取给定帐户的Money,并且将给定帐户的Money存入某些内存(有状态)。
def getActualMoney(accountNo: String): Integer = {...}
def saveActualMoney(accountNo: String, actMoney: Integer): Unit = {...}
所以我必须从“外面”获取我目前的钱。让我们说,我的程序正在以这种方式工作。现在我有两个同时请求,第一个:获得一些钱,第二个为同一个帐户存入一些钱。当然,我会得到两个不同的结果。因此存在竞争条件。
据我所知,我应该在此帐户“外部”编程代码上进行交易。所以,这种情况不应该发生。为了更好的并发性,函数看起来应该是这样的:
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)
这是怎么回事?值得做吗?
答案 0 :(得分:13)
虽然引用在技术上是正确的,因为你不能在纯函数代码中有竞争条件,你确实需要有副作用,以便程序除了计算一个值之外做任何有用的事情,所以它有点不诚实,但与此同时它确实有用。
以最小化副作用的样式进行编程,并且最大化纯粹的功能代码将减少代码区域,在这些区域中,竞争条件会发生在您知道自己的位置明确地使用副作用,这通常是纯函数式编程倡导者所吹捧的好处。
对于您的示例:如果有钱的帐户只是模拟只存在于您的程序中,那么通过让帐户上的每个操作都返回 new 具有更新值的帐户对象。如果你这样做,你将获得纯功能代码的好处:能够通过使用“等式推理”推理引用透明代码,这是你可以假设对函数的任何调用等同于值该函数返回给定它传递的参数,无论您按什么顺序调用它的次数。 (这是为了回答你的问题“它值得做吗?”)
但是,如果你在讨论一个系统,比如在外部数据库中存储账户和余额,那么你肯定需要副作用,你必须关注操作的顺序和潜在的竞争条件,就像你会用任何其他语言。但是,通过隔离您的副作用,您可以更加确定做必须关心此类并发问题的位置,并且还可以利用更高级别的并发抽象,例如MVars,IORefs,或者STM(在Haskell中可用,似乎是Scala),它鼓励在不纯的数据上使用纯操作 - 保持隔离和可组合。