函数式编程
为什么要学习函数式编程
为什么用不好函数式编程
想象一下自己是一个伐木工人,拿着斧头砍树,突然有人拿着电锯给我们砍树,而我们依旧按照斧子的方法用电锯,很快电锯被用坏了,然后我们抱怨这东西真差.
习惯于命令式编程的我们也拿着命令式的方式处理函数式编程
学习一种全新的编程范式,困难并不在于掌握新的语言.语法只是小细节,真正考验人的,是学会用另一种方式去思考.
范式转变
先上一段代码吧,对比一下传统编程和函数式编程
题目: 读入一个文本文件,确定所有单词的使用频率从高到低的使用频率排序
传统做法:
public class Words {
/**
* 虚词集合
*/
private Set<String> NON_WORDS = new HashSet<>(){{
add("the");add("and");add("of");add("to");add("a");
add("i");add("it");add("in");add("or");add("is");
add("i");add("s");add("as");add("so");add("but");add("be");
}};
public Map<String,Integer> wordFreq(String words){
Map<String, Integer> wordMap = new TreeMap<>();
Matcher m = Pattern.compile("\\w+").matcher(words);
while (m.find()){
String word = m.group().toLowerCase();
if(!NON_WORDS.contains(word)){
if(wordMap.get(word) == null){
wordMap.put(word,1);
} else {
wordMap.put(word,wordMap.get(word) + 1);
}
}
}
return wordMap;
}
}
函数式做法:
采用了java8中Stream API 和 以lambda块方式实现高阶函数
public class Words {
/**
* 虚词集合
*/
private Set<String> NON_WORDS = new HashSet<>(){{
add("the");add("and");add("of");add("to");add("a");
add("i");add("it");add("in");add("or");add("is");
add("i");add("s");add("as");add("so");add("but");add("be");
}};
private List<String> regexToList(String words,String regex){
List<String> wordList = new ArrayList<>();
Matcher m = Pattern.compile(regex).matcher(words);
while(m.find()){
wordList.add(m.group());
}
return wordList;
}
public Map<String> wordFred1(String words){
TreeMap<String,Integer> wordMap = new TreeMap<>();
regexToList(words,"\\w+").stream()
.map(w -> w.toCharArray())
.filter(w -> !NON_WORDS.contains(w))
.forEach(w -> wordMap.put(w,wordMap.getOrDefault(w,0) + 1));
return wordMap;
}
}
对比一下,两种写法: 第一种写法牺牲了代码的清晰来换取执行性能 第二种写法在性能上确实惨不忍睹,先经find产生的匹配结果转换成stream,然后再一步一步往下走,把原先一个循环转成了三次循环
命令式编程常常迫使我们处于性能考虑,把不同的任务 交织 起来,以便能够用一次循环来完成多个任务
而函数式编程用 map(), filter() 这些高阶函数把我们解放出来,让我们站在更高的抽象层次上去考虑问题,把问题看得更清楚
跟上语言发展的潮流
现在主流语言都进行了函数式方面的扩展,java也通过纳入 lambda 块(也就是高阶函数) 实现了函数式编程
将控制权让渡给语言/运行时
人总是越来越懒,从第一代编程语言发展到第四代编程语言,开发者们越来越多地把乏味单调的任务托付给语言和运行时(如Java中的泛型)
放弃例如内存管理,垃圾回收这类工作,把时间花在更高层次的抽象上,专注于更重要的问题,
函数式编程语言用高阶抽象从容取代基本的控制结构,将琐碎的细节交托给运行时,令繁琐的实现化作轻巧
简洁
**面向对象编程通过封装不确定因素来使代码能被人理解; 函数式编程通过尽量减少不确定因素来使代码能被人理解 ** —— working with legacy code 作者 Michael Feathers
所谓 不确定因素 就是为了精细地控制状态和改变方法而存在的封装,作用域,可见性等OOP的构造,以及多线程中对状态的控制.
而在函数式语言在这个问题上采用了另一种做法,他们认为,与其建立种种机制来控制可变状态,不如尽可能消灭可变的状态这个不确定因素.
其立论的根据在于 假如语言不对外暴露那么多有出错可能的特性,那么开发者就不那么容易犯错 这刚好很像 本质安全性 这个概念
在 面向对象的命令式编程语言里,重用的单元是类和类之间沟通用的消息,例如UML中依赖,继承
OOP的世界提倡开发者针对具体问题建立专门的数据结构,相关的专门操作以 方法 的形式附加在数据结构上
而函数式编程语言实现重用的思路很不一样; 函数式语言提倡在有限的几种关键数据结构(如list set map)上运用针对这些数据结构高度优化过的操作,以此构成基本的运转机构; 开发者再根据具体用途,插入自己的数据结构和高阶函数去调整机构的运转方式.
OOP 不断创建新的数据结构和附属操作,因为压倒一切的面向对象编程范式就是建立新的类和类之间的消息 把所有的数据结构都封装成类,一方面压制了方法层面的重用,另一方面鼓励了大粒度的框架式的重用
而函数式的程序构造更方便我们在比较细小的层面上重用代码.