1. 范式转变
1.1. 学习一种全新的编程范式,困难并不在于掌握新的语言
1.1.1. 真正考验人的,是怎么学会用另一种方式去思考
1.2. 计算机科学的间歇式进步,好思路有时搁置数十年后才变成主流
1.3. 第一种面向对象的语言Simula 67是1967年发明的,可是直到1983年诞生的C++终于流行起来以后,面向对象才真正成为主流
1.4. 早年Java总被认为太慢,内存耗费太高,不适合高性能的应用,如今硬件市场的变迁把它变成了极具吸引力的选择
1.5. 命令式编程风格常常迫使我们出于性能考虑,把不同的任务交织起来,以便能够用一次循环来完成多个任务
1.6. 函数式编程用map()、filter()这些高阶函数把我们解放出来,让我们站在更高的抽象层次上去考虑问题,把问题看得更清楚
2. 跟上语言发展的潮流
2.1. 函数式编程
2.2. Groovy已经具备了丰富的函数式特性,包括像“记忆”这样的高级特性在内
2.2.1. (memoization,指运行时自动缓存函数返回值的能力)
2.3. lambda块(也就是高阶函数)被纳入Java 8
2.4. C++在2011年版的语言标准里增加了lambda块,还有引人关注的Boost.Phoenix等类库
3. 把控制权让渡给语言/运行时
3.1. 第四代编程语言4GL下的一行命令,3GL可能要用很多行才写得出来,因为4GL自带了更丰富的编程环境
3.2. Java虚拟机(JVM)平台
3.2.1. Scala
3.2.2. Clojure
3.2.3. Groovy
3.3. NET平台
3.3.1. F#
3.4. 把时间花在更高层次的抽象上,多考虑怎样解决复杂的业务场景,少去费心复杂的底层运作
3.4.1. Java接管内存分配减轻了我们的负担
3.4.1.1. 人生苦短,远离malloc
3.4.2. 函数式编程语言用高阶抽象从容取代基本的控制结构
3.4.2.1. 将琐碎的细节交托给运行时,令繁冗的实现化作轻巧
4. 简洁
4.1. 面向对象编程通过封装不确定因素来使代码能被人理解
4.1.1. 精细地控制谁能够感知状态和改变状态
4.1.2. 大粒度的框架式的重用
4.1.2.1. 从类的关系中发现重复出现的模式并加以重用
4.1.2.1.1. 重用的单元是类和用作类间通信的消息
4.1.2.1.2. 用类图(class diagram)来表述
4.1.3. 喜欢大量地建立有很多操作的各种数据结构
4.1.3.1. Java世界的数十种XML类库,每一种都有自己定义的内部数据结构
4.2. 函数式编程通过尽量减少不确定因素来使代码能被人理解
4.2.1. 与其建立种种机制来控制可变的状态,不如尽可能消灭可变的状态这个不确定因素(moving parts)
4.2.2. 假如语言不对外暴露那么多有出错可能的特性,那么开发者就不那么容易犯错
4.2.3. 比较细小的层面上重用代码
4.2.3.1. 比起一味创建新的类结构体系,把封装的单元降低到函数级别,更有利于达到细粒度的、基础层面的重用
4.2.4. 函数式语言有很多的操作,但对应的数据结构却很少
4.2.4.1. 用很少的一组关键数据结构(如list、set、map)来搭配专为这些数据结构深度优化过的操作
4.2.4.2. 这些关键数据结构和操作组成的一套运转机构上面,按需要“插入”另外的数据结构和高阶函数来调整操作,以适应具体的问题
4.2.5. 函数式语言共同的一点小习惯:可以用表达式(expression)的地方就不用语句(statement)
5. 让语言去迎合问题
5.1. 让程序去贴合问题,不要反过来
5.2. Java不是一种特别灵活的语言,我们只能死板地用一些现成结构来拼凑自己的设计
5.2.1. 关键字蕴含了开发者无法从其他地方获得的语义
5.3. 另一些语言可塑性更强,他们不会拿问题去硬套语言,而是想法揉捏手中的语言来迎合问题
5.3.1. Scala
5.3.2. Clojure
5.3.3. Groovy
6. 分发机制
6.1. Java
6.1.1. 要表述“条件执行”
6.1.1.1. 很少的一些情况适用switch语句
6.1.1.2. 大多if语句
6.1.2. 依赖GoF模式集里面的Factory模式(或者Abstract Factory模式)来缓解问题
6.1.3. 没办法创造基本的语法构造单元,因此必须把问题翻译成符合编程语言语法的陈述
6.2. Groovy
6.2.1. switch语句在语法上模仿Java
6.2.1.1. 允许匹配区间和其他复杂类型
6.2.1.1.1. 功能要比Java的switch语句强大很多
6.3. Clojure
6.3.1. 开发者可以根据问题来修改语言,并没有一条明确的界线隔开语言设计者和使用语言来进行创作的开发者
6.3.2. 函数是比类更优先的语言成分
6.3.2.1. 它的函数调用用Java的眼光来看,就像内外颠倒了一样
6.3.2.2. Java下的score.toUpperCase()调用
6.3.2.3. 在Clojure下的等价写法是(.toUpperCase score)
6.3.3. 承载多态语义的多重方法(multimethod)特性允许开发者使用任意特征(及其组合)来触发分发
6.3.4. 多重方法赋予了Clojure构造强大分发机制的能力,其适应性不输于Java的多态,而且限制更少
6.3.5. 切断多态和继承之间的耦合关系
7. 运算符重载
7.1. Java
7.1.1. 没有运算符重载特性
7.1.1.1. 这是它的设计者在语言形成阶段就刻意作出的决定
7.2. Groovy
7.2.1. 将运算符自动映射成方法,从而令运算符重载变成了方法的实现问题
7.3. Scala
7.3.1. 完全不区分运算符和方法
7.3.2. 运算符不过是一些名字比较特别的方法
7.4. 现代语言大多已经相当程度地消除了定义上的复杂性,但以往关于滥用运算符重载的告诫都还是成立的
7.5. 要想契合问题域的表达习惯,可以利用运算符重载来改变语言的外貌,不必创造全新的语言
8. 函数式的数据结构
8.1. Java语言习惯使用异常来处理错误,语言本身提供了异常的创建和传播机制
8.2. “异常”违背了大多数函数式语言所遵循的一些前提条件
8.2.1. 函数式语言偏好没有副作用的纯函数
8.2.1.1. 抛出异常的行为本身就是一种副作用,会导致程序路径偏离正轨(进入异常的流程)
8.2.2. 引用的透明性(referential transparency)
8.2.2.1. 发出调用的例程不必关心它的访问对象真的是一个值,还是一个返回值的函数
8.2.2.2. 如果函数有可能抛出异常的话,用它来代替值就不再是安全的了
8.3. 函数式的错误处理
8.3.1. Either类
8.3.1.1. 不相交联合体(disjoint union)
8.3.1.2. 异常(如果有的话)置于Either类的左值上
8.3.1.3. 正常结果则放在右值
8.3.1.4. 可以容纳任意的内容
8.3.1.5. 返回结果(无论异常还是正常结果)需从Either中取出
8.3.1.5.1. 多了一道间接层
8.3.1.5.2. 实现缓求值的空间
8.3.1.5.3. 用来提供默认值
8.3.1.5.4. 用来包装异常
8.3.1.5.4.1. 将结构化的异常处理机制转化成函数式风格
8.3.2. Option类
8.3.2.1. 可以近似地看作Either类的一个子集
8.3.2.2. 表述了异常处理中较为简化的一种场景
8.3.2.2.1. none,表示不存在有效值
8.3.2.2.2. some,表示成功返回
8.3.2.2.3. 一般只用来表示成功和失败两种情况
posted @
2023-01-20 09:09
躺柒
阅读(
60)
评论()
编辑
收藏
举报