《函数式编程思维》书摘
函数式编程的基本函数
筛选(Filter)
scala
filter()
1
2
3val numbers = List.range(1, 11)
numbers filter (x => x % 3 == 0)
// List (3, 6, 9)1
2
3val numbers = list.range(1, 11)
numbers filter (_ % 3 == 0)
// List (3, 6, 9)partition()
1
2
3numbers partition (_ % 3 == 0)
// (List(3, 6, 9), List(1, 2, 4, 5, 7, 8, 10))find()
1
2numbers find (_ % 3 == 0)
// Some(3)takeWhile()
1
2List(1, 2, 3, -4, 5, 6, 7) takeWhile (_ > 0)
// List(1, 2, 3)dropWhile()
1
2List(1, 2, 3, -4, 5, 6, 7) dropWhile (_ > 0)
// List(5, 6, 7)
映射(map)
scala
map()
1
2List(1, 2, 3) map (_ + 1)
// List(2, 3, 4)flatMap()
1
2List(List(1, 2, 3), List(4, 5, 6)) flatMap(_.toList)
// List(1, 2, 3, 4, 5, 6)
折叠/化约(reduce)
scala
reduceLeft()
1
2List.range(1, 10).reduceLeft(0)(_ + _)
// 45reduceRight()
1
2
3
4
5
6
7
8
9
10List.range(1 ,10).reduceRight(_ - _)
// 8 - 9 = -1
// 7 - (-1) = 8
// 6 - 8 = -2
// 5 - (-2) = 7
// 4 - 7 = -3
// 3 - (-3) = 6
// 2 - 6 = -4
// 1 - (-4) = 5
// 5foldLeft()
1
2List.range(1, 10).foldLeft(0)(_ + _)
// 451
2(0 /: List.range(1, 10)) ( _ + _)
// 45foldRight()
1
2(List.range(1, 10) :\ 0)(_ - _)
// 5
责权让渡
迭代让位于高阶函数
用map等函数替换了迭代。这笔“交易”的得失很清楚:如果能够用高阶函数把希望执行的操作表达出来,语言将会把操作安排得更高效,甚至只要增加一行par修饰,就能够让操作并行化。
闭包(closure)
谓闭包,实际上是一种特殊的函数,它在暗地里绑定了函数内部引用的所有变量。换句话说,这种函数(或方法)把它引用的所有东西都放在一个上下文里“包”了起来。
这样说并不等于开发者应该抛开所有的责任,不去理解低层次抽象的来龙去脉。在很多情况下,我们使用一个抽象,比如Stream的时候,必须清楚可能产生的连带后果。很多开发者都没认识到,即使有了Java8的StreamAPI,他们仍然需要理解Fork/Join库的细节才能写出高性能的代码。当你掌握了背后的原理,才能把力量用在最正确的地方。
柯里化(currying)和函数的部分施用(partial application)
柯里化指的是从一个多参数函数变成一连串单参数函数的变换。
部分施用指通过提前代入一部分参数值,使一个多参数函数得以省略部分参数,从而转化为一个参数数目较少的函数。
柯里化和部分施用都是在我们提供部分参数值之后,产出可以凭余下参数实施调用的一个函数。不同的地方在于,函数柯里化的结果是返回链条中的下一个函数,而部分施用是把参数的取值绑定到用户在操作中提供的具体值上,因而产生一个“元数”(参数的数目)较少的函数。
递归
递归是以一种带点计算机科学味道的方式来对一组事物进行迭代,让事物的集合反复对自身调用同样的方法,使集合随着每次迭代不断缩小,同时要始终小心地保证退出条件的有效性。很多时候,我们的问题核心就是对一个不断变短的列表反复地做同一件事,把递归用在这样的场合,写出来的代码就容易理解。
Stream和作业顺序重排
从命令式风格转变为函数式风格还有一个潜在的好处,那就是运行时有能力在涉及效率的问题上替我们做决定。
在使用Java平台上的各种函数式方案的时候,我们必须保证传给filter()等函数的lambda块不存在副作用,否则可能导致无法预料的结果。允许运行时发挥其优化能力的做法,再次印证了我们关于交出控制权的观点:放弃对繁琐细节的掌控,关注问题域,而非关注问题域的实现。
函数式语言常见特性
记忆(memoization)
指的是在函数级别上对需要多次使用的值进行缓存的机制。
只有纯(pure)函数才可以适用缓存技术。纯函数是没有副作用的函数:它不引用其他值可变的类字段,除返回值之外不设置其他的变量,其结果完全由输入参数决定。
惰性求值(laziness)
缓求值(lazyevaluation)是函数式编程语言常见的一种特性,指尽可能地推迟求解表达式。缓求值的集合不会预先算好所有的元素,而是在用到的时候才落实下来,这样做有几个好处。第一,昂贵的运算只有到了绝对必要的时候才执行。第二,我们可以建立无限大的集合,只要一直接到请求,就一直送出元素。第三,按缓求值的方式来使用映射、筛选等函数式概念,可以产生更高效的代码。Java8以前的Java语言本身不支持缓求值,但平台上有一些框架和后继语言提供了这样的支持。
演化的语言
函数式编程语言和面向对象语言对待代码重用的方式不一样。面向对象语言喜欢大量地建立有很多操作的各种数据结构,函数式语言也有很多的操作,但对应的数据结构却很少。面向对象语言鼓励我们建立专门针对某个类的方法,我们从类的关系中发现重复出现的模式并加以重用。函数式语言的重用表现在函数的通用性上,它们鼓励在数据结构上使用各种共通的变换,并通过高阶函数来调整操作以满足具体事项的要求。
少量的数据结构搭配大量的操作
函数式的数据结构
函数式的异常处理
Either类
有了Either类,以在确保类型安全的前提下,视情况返回异常或者有效结果(但不会同时返回两者)。按照函数式编程的传统习惯,异常(如果有的话)置于Either类的左值上,正常结果则放在右值。
Option类
Option类表述了异常处理中较为简化的一种场景,它的取值要么是none,表示不存在有效值,要么是some,表示成功返回