一、函数式编程特点
- function is first class citizens,函数是头等公民,直白的说,函数也能作为参数使用. 最简单的例子就是foreach 、 sort函数
- no side affect. 所有的改变都在只在函数内部,不会改变任何东西,高内聚。你知道函数会做且会做什么,除此之外别无他物。举一个最简单反例: 可写的全局变量,一旦声明,很难对其进行约束,需要考虑很多问题(并发写、一致性、最大的问题:定位bug时很难确定是那段代码引起的错误)
-
Mutations is bad! 所有变量只有一次赋值,函数输出数据都是immutable(隐含的所有输入也都是immutable的),由于所有变量在生成(输出)后,都是immutable,所有的赋值操作都在函数内部。
二、为什么我们要用函数式编程
1、从算法出发思考问题,而不是从数据结构来思考问题。
举一个简单的例子,我们要产生小于10的所有偶数和奇数的数组,如果java写,可能就是这样:
import java.util.ArrayList;
import java.util.List;
public class MainJava {
public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>();
for (int i = 0; i < 10; i++) {
numbers.add(i);
}
List<List<Integer>> numberGroups = segregateNumbers(numbers);
List<Integer> evenNumbers = numberGroups.get(0);
List<Integer> oddNumbers = numberGroups.get(1);
System.out.println(evenNumbers);
System.out.println(oddNumbers);
}
private static List<List<Integer>> segregateNumbers(List<Integer> numbers) {
List<Integer> evenNumbers = new ArrayList<>();
List<Integer> oddNumbers = new ArrayList<>();
for (Integer number : numbers) {
if (number % 2 == 0)
evenNumbers.add(number);
else
oddNumbers.add(number);
}
ArrayList<List<Integer>> result = new ArrayList<>();
result.add(evenNumbers);
result.add(oddNumbers);
return result;
}
}
如果用functional programming的 scala来写:
object MainScala extends App {
val numberGroups = 0 to 10 partition (_ % 2 == 0)
println(numberGroups._1)
println(numberGroups._2)
}
把函数作为参数 & 返回值 (High order functions)
class List[T] {
// This Function takes anoter Function called "fn" as a parameter
// that it's applied to each of the elements within the List
// and filters them if fn returns false for that element.
def filter[T](fn: T => Boolean): List[T]
}
def times(time: Int): Int=>Boolean = {
return v => v % time == 0
}
val list: List[Int] = 0 to 9
val triples: List[Int] = list.filter(times(3))
构造复杂的组合函数
//1、读站列表:
void readSiteList(const char *, siteList, (*)(const char *))
//2、读取数据:
void readSite (const char *, (*)(const Data*))
//3、数据集齐:
void collect(const Data *, (*)(list<const Data *>))
//4、算法:
void algo (list<const Data *>, (*)(const Result *))
//5、网路:
void send2net(const Result *, (*)(const Result*))
6、本地写文件.
void write(const Result*, (*)(const Result*))
main = bind(readSiteList,_1,bind(readSite,_1, bind(collect,_1, bind(algo,_1,bind(send2net,_1,bind(write,_1,null))))))
2、代码查错容易
当你有一个很复杂的计算过程需要debug的时候,我们都是从最终结果数据开始,如果是面向对象的,你定位到底是哪一步错非常困难,(二分法),因为同一个数据有太多的地方会修改它,发现最终结果不对,可能要把所有流程都进行一遍排查,但是函数式编程就简单多了,你只要定位到出错数据,立刻可以找到出错的子函数(产生这个数据的函数),高效快捷的多。
3、天生支持的并行/异步计算
并行计算天生不是FP的,但FP天生就是支持并行计算的.为什么呢?因为所有输入数据都是immuatable,没有脏数据的问题,也没有并发写的问题。
spark / play / Kafka
说个题外话,Kafka,这个项目大家都知道,大数据时代的核心技术,可扩展、高容错的发布-订阅消息系统,它就是用scala开发的,而且是开发者的第一个scala项目,最初就是拿来练手的。