读函数式编程思维
总的说下
这本书总得讲了下函数式思维,以及用各种语言来掩饰了下函数式思维。。。后面我用 swift 来演示吧
第一章 为什么要用函数式思维
函数式思维是编程范式的转换,最重要的是我们要正确的掌握语言给我们提供的特性,才能把一个语言用好
随着计算机性能提升以及编译器的不断优化,可以把一些控制权(比如内存管理,多线程等琐碎的语言细节)交给运行时来处理,用更好和更简洁的抽象来解决我们的问题
第二章 转变思维
命令式编程是按照“程序是一系列改变状态的命令”来建模的一种编程风格。
比如 for
循环处理数组,我们平时使用的时候,是通过确定初始状态,然后每次迭代都执行循环的命令,在循环的命令里面我们完成业务逻辑
函数式方法,讲程序描述为表达式和变换,以数学方程的形式建立模型,并尽可能避免可变状态
通过将数据集,进行 filter,map,reduce 等转换,得到我们想要的结果,以一种定义或是描述式的方式来处理
所有的函数式语言里面都会以不同的形式提供 filter(过滤),map(映射),reduce(折叠或者化约)
第三章 责权让渡
函数式思维的好处之一,是能够将低层次细节(如垃圾收集)的控制权移交给运行时,从 而消弭了一大批注定会发生的程序错误。开发者们可以一边熟视无睹地享受着最基本的抽 象,比如内存,一边却会对更高层次的抽象感觉突兀。然而不管层次高低,抽象的目的总 是一样的:让开发者从繁琐的运作细节里解脱出来,去解答问题中非重复性的那些方面。
- 不再使用类似
for
,while
之类的迭代,而是使用高阶函数,比如 map - 闭包,关注函数执行的上下文,而非通过闭包去控制状态
- 柯里化,实现函数工厂,模板方法,隐藏参数
- 递归,递归更加优雅,以及更容易让我们考虑无限的集合
- Stream 和作业顺序重排
第四章 用巧不用蛮
记忆,和缓存类似,但是不需要我们来管理,只需要在函数调用后,告诉函数需要记忆此次调用的结果,下次使用相同参数调用的时候,就不需要再次进行计算了
swift 中的参考 memoize
缓求值,不到逼不得已,不去对函数进行求值。
缓求值可以处理无限长的序列,减少存储空间占用,缓求值还有利于运行时产生高效代码
第五章 演化的语言
在面向对象的世界里,我们针对具体问题,建立专门的数据结构,以方法的形式将专门的操作关联在数据结构上。
函数式编程,只使用很少的一组关键数据结构(list,set,map)搭配专为这些数据结构深度优化过的操作。
比起在定制的类结构上做文章,把封装的单元缩小到函数级别,有利于在更基础的层面上 更细粒度地实施重用。
Clojure 很好地发挥了这方面的优势,例如在 XML 的解析问题上。 Java 语言的 XML 解析框架数量繁多,每一种都有自己的定制数据结构和方法语义(如 SAX 和 DOM 都是自成一体)。Clojure 的做法相反 ,它不鼓励使用专门的数据结构,而是 将 XML 解析成标准的 Map 结构。而 Clojure 有极为丰富的工具可以与 map 结构相配合
使用模式匹配来替代长长的 if
要想契合问题域的表达习惯,可以利用运算符重载来改变语言的外貌,不必 创造全新的语言。
函数式数据结构,使用 Either 表示两种结果的返回值,使用 Option 来表示有为空返回值的类型
第六章 模式与重用
函数式语言有函数式语言的设计模式,传统的对于函数式语言来说,因为语言的特性,让部分模式变得没有意义了,但是部分问题还是存在,他们在函数式的世界里面,通过其他的方法来解决了
传统设计模式在函数式编程的世界中大致有三种归宿。
- 模式已被吸收成为语言的一部分。
- 模式中描述的解决办法在函数式范式下依然成立,但实现细节有所变化。
- 由于在新的语言或范式下获得了原本没有的能力,产生了新的解决方案(例如很多问题都可以用元编程干净利落地解决,但 Java 没有元编程能力可用)。
函数级别的重用
因为函数式编程的特点,重用的最小单位变成了函数,并且程序由多个函数组合而成。
Template Model,通过使用函数变量,进行了化简,减少耦合
Strategy 模式,更加灵活
Flyweight 模式,使用记忆来实现
Factory 模式和柯里化,柯里化就是产出函数的工厂
以结构为载体的代码重构需要考虑整个类的关系网,可以很好的减少耦合的情况。
第七章 现实应用
Java 8 中的函数式接口,Option 类型,Stream,都是函数式语言在 Java 语言中的应用
函数试的构架,贯彻“值不可变”,学习从值不可变的角度去思考
可变的状态与测试数量有直接的关联:可变的状态越多,要求的测试也越多。
实现一个值不可变的 Java 类,我们需要做到以下事情。
把所有的字段都标记为final。Java 要求被标记为 final 的字段,要么在声明时初始化,要么在构造器中初始化。不要 在意 IDE 大惊小怪地提醒我们字段没有在声明位置上初始化,当我们在构造器里写好 相关的初始化代码,IDE 就会明白过来。
把类标记为final,防止被子类覆盖。 如果类可以被覆盖,类中的方法也就有可能被改变行为,因此以防万一,我们干脆禁止 子类化。Java 的 String 类就采取了这样的防范策略。
不要提供无参数的构造器。 一个值不可变的对象,它的一切状态都必须通过构造器来设定。假如我们没有需要设定 的状态,那建立这么一个对象又有何必要呢?在无状态的类里面安排几个静态方法就足 够了。所以说,值不可变的类根本不应该出现无参数的构造器。假如我们受到某些框架 的限制,不得不提供无参数的构造器,这时可以考虑能否用一个私有的无参数构造器来 满足框架的要求(私有的构造器仍然可以通过反射来访问)。 JavaBeans 的标准规定要有默认构造器,我们摒除无参数构造器违反了这条规定。不过 反正 JavaBeans 里有各种 setXXX 方法存在,本身就不可能是值不可变的。
提供至少一个构造器。 构造器是我们在对象里添置状态的最后机会!
除了构造器之外,不要提供任何制造变化的方法。我们不但要避免沿袭 JavaBeans 风格的 setXXX 方法,还必须小心防范,不能返回任何 值可变的对象引用。标记了 final 的对象引用并不等于它所指向的一切都不可改变。因 此,我们需要预防性地复制所有通过 getXXX 方法返回的对象引用。
对于 web 框架,函数式语言回很合适,因为整个 web 就是一系列从请求到响应的变换