1.观点
1.1.抽象隐藏了繁杂的细节,只是有时候会连同重要的考虑因素一起隐藏掉
1.2.理解掌握的抽象层次永远要比日常使用的抽象层次更深一层
1.3.交出控制权的观点:放弃对繁琐细节的掌控,关注问题域,而非关注问题域的实现
2.函数式思维的好处
2.1.将低层次细节(如垃圾收集)的控制权移交给运行时,从而消弭了一大批注定会发生的程序错误
2.2.函数式语言的简洁语法和灵活配合,才使递归成为简单可行的代码重用选项之一
2.3.运行时有能力在涉及效率的问题上替我们做决定
2.4.从频繁出现的场景中消灭掉烦人的实现细节
3.闭包(closure)
3.1.一种特殊的函数,在生成的时候,会把引用的变量全部圈到代码块的作用域里,封闭、包围起来(故名闭包)
3.1.1.闭包作为一种对行为的建模手段,让我们把代码和上下文同时封装在单一结构,也就是闭包本身里面,像传统数据结构一样可以传递到其他位置,然后在恰当的时间和地点完成执行
3.2.闭包的每个实例都保有自己的一份变量取值,包括私有变量也是如此
3.2.1.代码块实例从它被创建的一刻起,就持有其作用域内一切事物的封闭副本
3.3.在缺乏闭包特性的旧版Java平台上,Functional Java利用匿名内部类来模仿“真正的”闭包的某些行为,但语言的先天不足导致这种模仿是不彻底的
3.4.当作一种异地执行的机制,用来传递待执行的变换代码
3.5.是推迟执行原则的绝佳样板
3.6.抓住上下文,而非状态
3.6.1.“让运行时去管理状态”
4.柯里化(currying)和函数的部分施用(partial application)
4.1.向一部分参数代入一个或多个默认值的办法来实现的
4.1.1.这部分参数被称为“固定参数”
4.2.柯里化
4.2.1.从一个多参数函数变成一连串单参数函数的变换
4.2.2.结果是返回链条中的下一个函数
4.3.部分施用
4.3.1.通过提前代入一部分参数值,使一个多参数函数得以省略部分参数,从而转化为一个参数数目较少的函数
4.3.2.把参数的取值绑定到用户在操作中提供的具体值上,因而产生一个“元数”(参数的数目)较少的函数
4.4.Groovy
4.4.1.curry()函数实现柯里化
4.5.Clojure
4.5.1.(partial f a1 a2 …)函数
4.5.2.没有将柯里化实现成一种语言特性,相关的场景交由部分施用去处理
4.6.Scala
4.6.1.柯里化
4.6.2.部分施用函数
4.6.3.偏函数
4.6.3.1.PartialFunction trait是为了密切配合语言中的模式匹配特性
4.6.3.2.trait并不生成部分施用函数。它的真正用途是描述只对定义域中一部分取值或类型有意义的函数
4.6.3.3.Case语句是偏函数的一种用法
4.6.3.4.偏函数的参数被限定了取值范围
4.6.3.5.可以把偏函数用在任何类型上,包括Any
4.7.大多数函数式语言都具备柯里化和部分施用这两种特性,但实现上各有各的做法
4.8.用途
4.8.1.函数工厂
4.8.1.1.工厂方法的场合,正适合柯里化(以及部分施用)表现它的才干
4.8.2.Template Method(模板方法)模式
4.8.2.1.在固定的算法框架内部安排一些抽象方法,为后续的具体实现保留一部分灵活性
4.8.3.隐含参数
5.递归
5.1.以一种自相似的方式来重复事物的过程
5.2.对一个不断变短的列表反复地做同一件事,把递归用在这样的场合,写出来的代码就容易理解
5.3.递归操作往往受制平台而存在一些固有的技术限制,因此这种技法绝非万灵药
5.4.但对于长度不大的列表来说,递归操作是安全的
5.5.语言在管理返回值,它从递归栈里收集每次方法调用的返回结果,构造出最终的返回值
5.6.利用递归,把状态的管理责任推给运行时
6.记忆(memoization)
6.1.用更多的内存(我们一般不缺内存)去换取长期来说更高的效率
6.1.1.缓存可以提高性能,但缓存有代价:它提高了代码的非本质复杂性和维护负担
6.1.2.负责编写缓存代码的开发者不仅要顾及代码的正确性,连它的执行环境也要考虑在内
6.1.3.代码中的状态,开发者不仅要费心照应它,还要条分缕析它的一切明暗牵连
6.2.记忆的内容应该是值不可变的
6.3.保证所有被记忆的函数
6.3.1.没有副作用
6.3.2.不依赖任何外部信息
6.4.只有纯(pure)函数才可以适用缓存技术
6.4.1.纯函数是没有副作用的函数
6.4.1.1.它不引用其他值可变的类字段
6.4.1.2.除返回值之外不设置其他的变量
6.4.1.3.其结果完全由输入参数决定
6.4.2.只有在函数对同样一组参数总是返回相同结果的前提下,我们才可以放心地使用缓存起来的结果
6.5.缓存是很常见的一种需求,同时也是制造隐晦错误的源头
6.6.两种情况
6.6.1.类内部缓存
6.6.1.1.类中的缓存就代表类有了状态,所有与缓存打交道的方法都不可以是静态的,于是产生了更多的连锁效应
6.6.2.外部调用
6.7.两种实现方式
6.7.1.手工进行状态管理
6.7.2.采用记忆机制
6.8.在命令式的思路下,开发者是代码的主人(以及一切责任的承担者)
6.9.我们写出来的缓存绝不可能比语言设计者产生的更高效,因为语言设计者可以无视他们给语言定的规矩:开发者无法触碰的底层设施,不过是语言设计者手中的玩物,他们拥有的优化手段和空间是“凡人”无法企及的
6.9.1.上帝视角
6.10.Groovy
6.10.1.先将要记忆的函数定义成闭包,然后对该闭包执行memoize()方法来获得一个新函数,以后我们调用这个新函数的时候,其结果就会被缓存起来
6.10.2.memoizeAtMost(1000)
6.11.Clojure
6.11.1.(memoize )
6.12.Scala
6.12.1.没有直接提供记忆机制,但它为集合提供的getOrElseUpdate()方法已经替我们承担了大部分的实现工作
6.13.Java 8
6.13.1.没有直接提供记忆特性,但只要借助它新增的lambda特性,就可以轻松地实现记忆功能
7.缓求值(lazy evaluation)
7.1.尽可能地推迟求解表达式
7.1.1.昂贵的运算只有到了绝对必要的时候才执行
7.1.2.可以建立无限大的集合,只要一直接到请求,就一直送出元素
7.1.3.按缓求值的方式来使用映射、筛选等函数式概念,可以产生更高效的代码
7.1.4.减少占用的存储空间。假如能够用推导的方法得到后续的值,那就不必预先存储完整的列表了——这是牺牲速度来换取存储空间的做法
7.2.非严格求值(non-strict)的(也叫缓求值,lazy)
7.2.1.常用的非严格求值语言有Haskell
7.3.Totally Lazy框架(Java)
7.4.Groovy
7.4.1.缓求值列表是函数式语言普遍具备的特性
7.4.1.1.LazyList
7.4.2.暂缓初始化昂贵的资源,除非到了绝对必要的时候
7.4.3.可以用来构建无限序列,也就是没有上边界的列表
7.4.4.缓求值列表特别适用于资源的生产成本较高的情况
7.5.Clojure
7.5.1.数据结构都是默认缓求值的
7.6.Scala
7.6.1.没有把一切都默认安排成缓求值的,而是在集合之上另外提供了一层缓求值的视图
7.7.缓求值的字段初始化
7.7.1.Scala
7.7.1.1.val声明前面加上“lazy”字样
7.7.1.1.1.令字段从严格求值变成按需要求值
7.7.2.Groovy
7.7.2.1.抽象语法树(Abstract Syntax Tree,AST)
7.7.2.1.1.@Lazy标注
8.元函数技法
8.1.操纵的对象是函数本身,而非函数的结果
8.2.柯里化