函数式编程中的纯函数
Posted 折花
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了函数式编程中的纯函数相关的知识,希望对你有一定的参考价值。
初次涉及函数式编程,鉴于以往的经验,我选择首先将理论和优秀实践的基础打得扎实一些。说些题外的,在现在的实际工作编码中存在一些脏代码问题,这一方面是因为个人经验不足,同时也很大程度上对编程的理论不够扎实,因此做不到clean code。
在纯函数之前,我们承接上文的脏状态来理解一下函数的副作用。在编程中,函数副作用是指当前函数除了计算结果之外,还产生了其他操作,例如IO、修改全局变量、数据持久化等等。在一些成规模的项目中,这些副作用会在测试、复用、并行化、推导和泛化上造成困扰。
副作用的困扰
以书上购买咖啡为例,一个有副作用的函数会做些什么呢?接受信用卡参数、产生咖啡、信用卡付费、返回咖啡。
class cafe {
def buyCoffee(card: CreditCard)={
val cup = new Coffee("cap",56)
card.charge(cup.price)
cup
}
}
case class CreditCard(count: Double){
def charge(price: Double)={
this.count-price
//获取银行授权、计费、持久化交易记录
}
}
case class Coffee(coffeeName: String,price: Double)
届时我们如果要测试这段代码的话就真的要准备多付费几次了。按理说,信用卡不应该知道联系银行执行计费,我们需要让信用卡对象忽略这些事情,并且在今天,信用卡也不单单只有一种支付手段了,我们需要实现支付对象,使代码更加模块化和可以测试。
这样也更加贴近实际生活,我们购买咖啡的时候不光要出示信用卡,还要选择一个支付方式。这样虽然仍然存在副作用,但是至少可以测试了。
我们使用函数式的方式来解决这个问题,函数式的解法是通过将函数中的副作用推到程序外面来消除副作用。举个例子,在购买咖啡函数中,我们不仅要返回一杯咖啡,还要返回咖啡的花费,之后再程序的外面处理花费(通知银行扣费、持久化交易记录)。
class cafe {
def buyCoffee(card: CreditCard)={
val cup = new Coffee("cap",56)
card.charge(cup.price)
cup
}
def fpBuyCoffee(card: CreditCard):(Coffee,Charge)={
val cup = new Coffee("cap",56)
(cup,Charge(card,cup.price))
}
}
case class CreditCard(count: Double){
def charge(price: Double)={
this.count-price
//获取银行授权、计费、持久化交易记录
}
}
case class Coffee(coffeeName: String,price: Double)
case class Charge(card: CreditCard,price: Double)
到这里我们窥见了函数式程序的大体架构,即一个纯函数的核心和薄的外围,从这个角度看我们改造后的购买咖啡函数:fbBuyCoffee就是核心纯函数,而外围就是处理charge的函数,像付费和持久化等。这里注意的是case类是消息类。
纯函数是什么
这里给出对纯函数的理解:一个函数在执行过程中除了根据参数计算结果之外,没有其他影响,这类函数就被称为纯函数。
引用透明
我们使用引用透明的概念对纯函数进行形式化。引用透明要求函数不论进行了什么操作都可以用它的返回值代替。程序中2+3都可以代换成5,程序就像代数式一样可以等式推理。这种代换模型更加的易于推理,所有的运算的影响都是局部性的,没有副作用所带的乱七八糟的全局影响,每一次的结果都是一致的。 纯函数就是将“如何处理结果”和“获得输出”进行分离,使计算更容易复用。
:StringBuiler的append方法是不是一个纯函数?
以上是关于函数式编程中的纯函数的主要内容,如果未能解决你的问题,请参考以下文章