Wikipedia 对函数式编程的定义是:
Functional programming is a programming paradigm that treats computation as the evaluation of mathematical functions and avoids state and mutable data. It emphasizes the application of functions, in contrast to the imperative programming style, which emphasizes changes in state.
函数式编程是一种编程范式,在这种编程方式中,我们更多的使用函数运算。函数运算的特点是没有状态和可变量,一个函数有输入值和输出值,运算一个函数不会产生任何副作用(side effect)。
与之相对的是Imperative Programming, 就是我们通常说的命令式编程。一般我们的编程方式都是命令式的,每一段程序都是指令,明确告诉计算机要做什么,运行指令的结果往往是程序状态(state)的改变。
一个例子最好说明两者的区别。让我们来打印八卦的符号。对程序员来说,这是一个很简单的作业。典型的程序会是这样的:
--------------------------------------------------------------------------------
#1 let yinyang = ['¦';'|']
#2 for x in yinyang do
#3 for y in yinyang do
#4 for z in yinyang do
#5 printf "%c%c%c " z y x
Output:
¦¦¦ |¦¦ ¦|¦ ||¦ ¦¦| |¦| ¦|| |||
--------------------------------------------------------------------------------
这里我用的是F#语言,一种函数式编程语言,但是它也可以用命令式的方式编程。我们用嵌套的for语句打印出每一爻,我们告诉计算机,第一爻有阴阳两种情况,第二爻有阴阳两种情况,第三爻有阴阳两种情况,循环后我们得到八种可能,然后打印结果。
这好像没有任何惊奇的东西。其实这里有一个很大的问题,这个问题就是,我们在编程之前就已经知道了结果!八卦是如何产生的?“太极生两仪,两仪生四象,四象生八卦,八卦生万物。”这个过程并没有在我们的程序中体现出来。世界的本质是变易,生生不息,周而复始。变易有其规律,比如牛顿万有引力定律,广义相对论,能量守恒定律等等。数学和物理学试图用函数的方式来描述这些规律,而我们的程序也可以以这种方式来编写。
下面是另一种方式来生成和打印八卦符号。
--------------------------------------------------------------------------------
#1 let generate a = a |> List.collect (fun x -> ['¦'::x; '|'::x])
#2
#3 [[]] |> generate
#4 |> generate
#5 |> generate
#6 |> List.map (List.fold (fun acc x -> acc + x.ToString()) "")
#7 |> List.iter (printf "%s ")
Output:
¦¦¦ |¦¦ ¦|¦ ||¦ ¦¦| |¦| ¦|| |||
--------------------------------------------------------------------------------
第一行定义了一个函数generate,它有一个输入值a,等号右边是函数的定义。关键部分是其中的一个变换函数:
fun x -> ['¦'::x; '|'::x]
这个函数从每个输入值x生成了两个新的元素, 这两个新元素分别是由在x前面加上'¦'和'|'而生成的。
|> 是pipe-forward操作符(operator)。如果你熟悉Unix命令行,你对pipe应该很了解。它的作用就是将前面的值传送给后面的函数来处理。这其实是一种写法上的简化, 事实上|> 的定义是:
let (|>) x f = f x
|>的好处是可以将pipe连接起来用,程序看上去很直观,比如
x |> f1 |> f2 |> f3
这相当于
f3(f2(f1(x)))
回到上面的程序,第三行就是“太极生两仪”:
[[]] |> generate
第四行是“两仪生四象”,第五行是“四象生八卦”。第六、七行是用来打印结果,在这里不是关键的。
这个例子中有一些语法和模块函数细节(比如List.collect, List.map),这里暂不做详细说明,这篇文章的目的是想说明函数式编程与命令式编程的区别。函数式编程有很明显的特征:
1. 函数操作数据,函数没有副作用(side effect);
2. 程序由一系列函数对数据的变换构成;
3. 没有变量,只有输入值和输出值;
在第二个例子里面,我们从空([])生成了两仪,从两仪生成了四象,从四象生成了八卦,所有这些中间结果以及生成过程都在程序中体现出来。而在第一个例子里面没有。所以说函数式编程似乎更切合宇宙运行的本质,即变易的本质。