本系列文章以我的个人博客的搭建为线索(GitHub 仓库:Evian-Zhang/evian-blog),记录我在现代化程序设计中的一些笔记。在这篇文章中,我将尽量用通俗易懂的方法,介绍函数式编程的入门知识。
什么是函数式编程
作为一个开发者,面对一个需求,我们掌握的知识常常是一定能完成这个需求,但是完成的方法多种多样,我们需要选择更好的技术来完成这些需求。就比如说我们是做汉堡的厨师,那么只要具有这方面的知识,这汉堡总能做成,无非是将制作好的肉、酱、蔬菜放在两片面包之间。但是,完成这个汉堡的手法有多种,比如说先加蔬菜,再加酱,最后加肉,再比如说加烤制的肉,速冻的蔬菜,在这些方法间权衡,才能做出最优的汉堡。
函数式编程就是一种方法,它并非一种特定的语言,而是一种理念,一种技术,让我们在编程开发的过程中,能够多一种挑选权衡的思路。函数式编程能让我们更清晰地完成我们的代码,在学习函数式编程的时候对于我们的编码思维也是一种提升,很多人往往学了函数式编程就离不开了。那么,我就介绍一下函数式编程的入门级知识。
假设我们使用Rust语言,在完成我们汉堡的制作:
struct Hamburger { }
impl Hamburger {
fn make_hamburger(meat: Meat, vegetable: Vegetable, sauce: Sauce) -> Hamburger { /* hide */ }
}
fn main() {
let mut hamburger_count = 0;
let mut make = |meat, vegetable, sauce| {
hamburger_count += 1;
Hamburger::make_hamburger(meat, vegetable, sauce)
}
let hamburger1 = make_hamburger(Meat::new(), Vegetable::new(), Sauce::new());
let hamburger2 = make_hamburger(Meat::new(), Vegetable::new(), Sauce::new());
}
这是我们最初的设计思路。
基本思想
函数式编程有一个最基本的思想,函数是一等公民,我们可以像传递别的变量一样,在函数的参数、返回值中传递函数。这一点绝大多数的编程语言都已经支持了,而且十分符合逻辑(事实上,函数不是一等公民才不符合逻辑),所以这一点就不再赘述了。
纯函数
在函数式编程中,函数作为它的招牌,必然有很多理念。首先,我们介绍的是纯函数。
我们在数学中遇到过函数,在编程中也遇到过函数,这两个函数有什么不同点呢?我们有一个数学里的函数 f ( x ) = x + 1 f(x)=x+1 f(x)=x+1, 在编程中,我们有这个函数:
int f1(int x) { return x + 1; }
这和我们数学里的函数好像差不多噢。
那我们改写一下:
int y = 0;
int f2(int &x) {
int adder;
printf("Input a number: ");
scanf("%d", &adder);
int z = x + adder;
x -= 1;
y++;
return z;
}
f1
函数和我们的数学里的函数是完全一致的,而f2
则不同,它有两个重要的不同点:
f2
改变了x
和y
的值。- 对于相同的
x
,f2
的输出结果是不确定的。
这样的函数十分不利于我们的编程,因为它在内部改变了状态,也会改变外部的状态,而如果我们并不知道,那么擅自调用可能会导致几百行之外的某个地方出问题。
因此,我们有纯函数的概念,它要求:
- 函数无副作用,即不改变全局变量,捕获的变量,或传入的可变引用
- 如果输入相同,那么输出相同
这样的函数就可以比较清晰地反应我们的思路。
观察上面制作汉堡的流程,我们的make
函数似乎并不是纯函数。它改变了外部捕获的变量hamburger_count
。因此,我们可以这样将它改变成纯函数:
struct Hamburger { }
impl Hamburger {
fn make_hamburger(meat: Meat, vegetable: Vegetable, sauce: Sauce) -> Hamburger { /* hide */ }
}
fn main() {
let hamburger_count = 0;
let make = |meat, vegetable, sauce, hamburger_count| {
(Hamburger::make_hamburger(meat, vegetable, sauce), hamburger_count + 1)
};
let (hamburger1, hamburger_count) = make_hamburger(Meat::new(), Vegetable::new(), Sauce::new(), hamburger_count);
let (hamburger2, hamburger_count) = make_hamburger(Meat::new(), Vegetable::new(), Sauce::new(), hamburger_count);
}
变量不可变
在刚刚解决纯函数的问题时,我们发现改完代码以后,原来代码里的mut
都没有了,所有的变量都成了不可变的变量。这自然也符合函数式编程的思想——变量不可变。在如Haskell等纯函数式编程语言中,这是强制的,但在现在流行的一些编程语言中,也提供了这样的支持,无论是Rust的let
与let mut
, Swift的let
与var
,Kotlin的val
与var
,JavaScript的let
与const