理解函数式编程的本质

6,938 阅读5分钟

functional programming

前言

笔者在之前曾经花时间学习了Haskell(所谓的纯函数式编程语言),并且在也研究过一段时间的函数式响应编程,也就是Functional reactive programing,总体有直观的感觉,但最近在学习kotlin的过程中,突然对于函数式编程又引起了新的思考,于是花时间去研究了一番,究竟什么是函数式编程,总结成文希望能够帮助你理解。

一些理解存在偏差

函数式编程在各个领域经常被提及,但很多文章去讲函数式编程都会有些偏差,并没有抓到本质。或者说讲了函数编程常见的写法,但没有讲清楚究竟什么是函数式编程。只有理解了这个本质问题,才能在后续的学习中一帆风顺。

(为了理解函数式编程,本文后面一些名词直接采用英文名词进行解释说明,因为生硬的翻译过来,会加大理解的难度)

什么是函数(Function)

这个问题虽然看上去很简单,但其实并不简单。

函数(function)这个名词来自于数学,函数通过一个给定的值,计算出另外一个值,也就是上学时常见的f(x)。

在通常的理解中,下面代码里面,f1,f2,f3通常都被叫做函数:

//伪代码 函数无入参,返回2
def f1(): return 2
//函数有参数x,返回 x+1
def f2(int x): return x+1
//函数无入参,无返回值,打印hello world
def f3(): print("hello world")

但实际上,函数(function)和procedure是有区别的: function 通过运算返回一个值,而procedure只执行一段代码,没有返回值。 这一点对于后面的理解是非常有帮助的,首先要区分出二者。

再回到上面的代码中,f1,f2,为function而f3为procedure。

什么是Pure Function

Pure:纯的; 单纯的; 纯真的; 干净的 我们将满足下面两个条件的函数称作Pure Function:

  1. 函数不会产生side effect(no side effect)
  2. 函数满足referential transparency这个条件 (原谅我不会翻译这两个名词)

Side effect

函数调用后不会对外部状态产生影响,比如下面这段代码中sum函数是no side effect的:

def sum(a,b): return a+b

产生side effect的函数长成什么样呢?其实经常会写这样的函数:

int sum = 0
def plus(a,b){
  sum = a + b
  return sum
}

plus函数除了计算参数之和以外,还改变了外部变量sum的值,我们plus这个函数产生了side effect。

常见的Side effect

  • 改变外部变量的值(上面的例子中plus函数)
  • 像磁盘中写入数据
  • 将页面上的一个按钮设置为可点击,或者不可点击

前面提到function和procedure的不同点,在side effect这个角度来讲,pure funcion不会产生side effect,procedure通常会产生side effect。

Referential transparency

Referential transparency means that given a function and an input value, you will always receive the same output. That is to say there is no external state used in the function.

Referential transparency means that you can replace anyexpression in the program with the result of evaluating that expression (or vice versa) without changing the meaning of the program.

  • 满足Referential Transparency的函数可以将可以将用函数计算的结果替换表达式本身,而不影响程序的逻辑。
  • 给定指定的参数,在任何时候返回的值都是相同的。不受其他外部条件影响。

两者说的意思是一样的,只是表达的角度是不同的

举个满足RT的例子

def f(): return 2

print(f() + f())
print(2)

下面这段代码中的f()是满足RT的函数,按照上面的解释,我们可以将f()的结果也就是2替换掉f(),不会影响程序本身的逻辑:

def f(): return 2

print(2 + f())
print(2)

或者这样替换:

def f(): return 2

print(f() + 2)
print(2)

从另一个角度说,f()这个函数无论在什么时候调用,返回的值都是一样的,不会发生改变(没有外部条件影响)

举个不满足RT的例子

int counter = 0

def f(x){
  counter += 1
  return x + counter
}

这个例子中,f(x)这个函数不满足RT 下面的代码中,当我们用f(1)的计算结果一次替换代码中f(1)本身时,程序的逻辑是错误的:

//原始的代码执行结果是:3
f(1) + f(1)

//把f(1)的结果1替换进来,下面函数执行的结果是:2
f(1) + 1

//同样,得到2
1 + f(1)

//得到2
1 + 1

我们不能用执行的结果替换函数本身,

换个角度,下面两行代码执行的结果也不同

f(1) + f(1)
2 * f(1)

虽然入参都为1,但f(1)在不同时候调用得到的结果不同,因此f不满足RT这个条件

回到pure function

理解了side effect 和 referential transparency的含义,我们再来重温pure function的定义,就很好理解了:

  • No side effect
  • Referential transparency

满足这两个条件的函数,称之为pure function

什么是函数式编程

理解了pure function之后,我们回到什么是functional programing,就会有种豁然开朗的感觉:

Functional programming is about writing pure functions

函数式编程最大限度的写pure function,让函数最大限度的减少side effect,并且保证函数在任何时候传递相同参数时,得到的结果都相同。

有什么好处

当我们按照函数式编程的思想编写程序时,程序具有天然的模块属性,因为实现的函数为pure function,你可以任意组合这些pure function。

pure function不会产生side effect,不需要对外部的状态产生顾虑。

其他好处在很多文章中都有提到,后续有时间笔者再来分析。:-D

总结

希望通过前面几个核心概念的讲解,能够让你明白函数式编程的本质,如果有任何文中写的有偏差的地方,欢迎讨论,👏