diff options
| -rw-r--r-- | content/library/2026/functional-design.md | 51 |
1 files changed, 51 insertions, 0 deletions
diff --git a/content/library/2026/functional-design.md b/content/library/2026/functional-design.md new file mode 100644 index 00000000..ed81c808 --- /dev/null +++ b/content/library/2026/functional-design.md @@ -0,0 +1,51 @@ ++++ +title = 'Functional Design' +slug = '' +tags = [] +authors = ['Robert C. Martin'] +rating = 3.5 +date = 2026-05-14T11:35:24+08:00 +cover = 'https://img.eltr.ac/geedeapro/library/2026/functional-design/cover.jpg' +draft = true +toc = true +toot = '' ++++ + +一本讲函数式编程以及在函数式语言中应用设计原则、设计模式和整洁架构的书,书本使用的编程语言主要是 Clojure。我得说,这本书从讲完函数式编程是什么之后就散发着面向对象的气味,书中提供的设计模式既 FP 又 OOP,但我想这是更务实的选择,真实的软件不可能是纯函数的。<!--more--> + +## 对翻译的…… 哦等等 + +我读的原文来着。其实鲍勃大叔的语言很通俗,甚至说偏口语化,读起来很轻松。 + +## 什么是函数式编程? + +在《[整洁架构之道](/library/2026/clean-architecture/)》中,鲍勃大叔提及了三种编程范式,函数式编程就是其中的一种,另外两种分别是结构化编程和面向对象编程。不过,本书中的函数式编程更像是一种「风格」而非强硬的规范。 + +函数式程序的一个重要特征是引用透明性(Referential transparency),意思是程序员应该能够像替换数学表达式里的函数一样,把程序里的某个函数引用换成具体的值(一般是函数的返回值),并且还能保证程序的行为不改变。要做的这点,就必须要确保状态的不可变性(Immutability)。 + +假设函数 `f` 接收参数 `x`,返回 `y`,如果能把 `f(x)` 直接替换为 `y`,这部分程序就是函数式的。假设 `f` 读取某个全局变量 `z`,返回 `x + z`,那么系统的状态(`z`)就会影响 `f(x)` 的返回值,此时就找不到一个 `y` 可以在保证系统行为不改变的情况下替换 `f(x)` 了——这个程序不是函数式的。 + +状态可变会导致很多问题。首先是时序耦合(Temperal Coupling),即代码执行的先后顺序会影响程序的行为。 + +> It is something you are probably quite familiar with. `Open` must be called bfore `close`. `New` must be called before `delete`. `Malloc` must be called before `free`. The list of pairs like this is endless. And in many ways, they are a bane of our existence. + +你有多少次发现某个程序问题只需要调换两个函数调用的顺序就能解决?人们熟悉的垃圾回收机制(Garbage Collection)就是因为人们管理不好时序耦合、管理不好已经分配的内存,才会被广为接受。 + +另一个问题是并发带来的竞争条件(Race Condition),如果多个进程或线程同时读写一块内存区域(或者说系统状态),最终得到的结果极有可能是不正确的,结果依赖于进程或线程的执行顺序——仔细想想,这实际上也是时序耦合带来的,如果顺序不重要,结果就不会因为顺序改变而发生改变。一般解决这个问题的方式是加锁,而锁是痛苦的。 + +如果状态不可变,上述问题都不会发生。 + +> You can't have race condition if you don't mutate state. + +简而言之,函数式编程就是禁止(或者至少在极大程度上限制)赋值操作的编程。函数式语言里的变量实际上不可变,只能被初始化而不能被赋值,它们是常量。函数式程序不改变状态,状态变更是在一个接一个的递归函数调用中以参数的形式被传递的,过往的状态不会改变,我们只创建新的状态。 + +## 在有限图灵机上作弊 + +邱奇-图灵论题(Church–Turing thesis)表明递归、λ 演算和图灵机是等价的,即 λ 表达式和图灵机程序(也就是任何用图灵完备的编程语言编写的程序)可以相互转换。然而,我们使用的计算机是有限图灵机(finite Turing machine),函数式程序是看起来像 λ 表达式的程序,但实际上是在图灵机上实现的。为了实现这种编程风格,我们需要作弊。 + +首先是尾部调用优化(TCO,Tail Call Optimization),因为函数式编程使用递归而非循环(如果想禁止赋值语句,那就必须递归)。如果递归的次数太多,函数调用栈就会占用大量的内存,比方说,在遍历一个很长的列表的时候。如果递归调用在函数的尾部,函数式编程语言的编译器就会优化这段代码,新的递归不会创建新的栈帧(Stack Frame),而是用 `goto` 语句跳转到函数顶部,复用当前的栈帧。 + +这实际上还是在使用循环,从执行的层面来看,这个程序和那些使用结构化编程的程序没什么区别,但从源代码来看,递归的方法就是函数式的。所以函数式编程是一种风格,出于各种现实的原因,程序实际上还是顺序执行。 + + + |
