函数式编程 入门指南
函数式编程 入门指南
# 函数式编程 简介
# 什么是 函数式编程
函数式编程,Functional Programming,简称 FP,
是一种编程范式。
它将电脑运算视为函数运算,并且避免使用程序状态以及易变对象。其中,λ 演算(lambda calculus)为该语言最重要的基础。而且,λ 演算的函数可以接受函数当作输入(引数)和输出(传出值)。
比起指令式编程,函数式编程更加强调程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而不是设计一个复杂的执行过程。
它涉及很多数学上的学科:
- 抽象代数
- 群论
- 范畴论
但 FP 和 OOP 的关系也不是高低贵贱之分,而是风格差异。
这些思想不是教条,而是前人为了写出更好的代码进行的思考和总结。
在学习 FP 的过程中也一定会碰到和 OOP 在设计模式上的对比。
在实际业务中,经常会不得不和副作用打交道。
(网络请求,用户事件,I/O 等)
前端中 JS 是基本语言,JS 是一个多范式的编程语言,
初学 FP,可以先保持原有的编码习惯,并从计算层逐渐切入。
也没有必要教条地遵循 FP 的开发模式,
如:完全不使用 if
/for
、只用 const
等新手 FP 装逼行为。
因为从编程语言设计的层面来说(如编译优化),JS 不完全适用于纯 FP 模式。
# 代码风格
函数式编程和其他范式之间的风格差异
在 JavaScript 中:
# 函数式 vs 面向对象
// FP,函数、传参
grow(circle, 3);
// * OOP,对象、方法
circle.grow(3);
2
3
4
5
# 函数式 vs 指令式
找出大于 35 岁的程序员的名字
const people = [
{ name: 'Bob Martin', age: 68 },
{ name: 'Dan Abramov', age: 27 },
{ name: 'Joel Spolsky', age: 55 },
];
// => [ 'Bob Martin', 'Joel Spolsky' ]
2
3
4
5
6
函数式:
// * 拆分成子任务,纯函数、函子、无状态
const result = people
.filter((p) => p.age > 35)
.map((p) => p.name);
2
3
4
指令式:
// * 手动处理运算过程,循环结构、外部变量
const result = [];
for (let i = 0; i < people.length; i++) {
const p = people[i];
if (p.age > 35) result.push(p.name);
}
2
3
4
5
6
# 为什么要学习 函数式编程
- 当你学习一些新兴前端工具,你需要了解基本的 FP
- FP 的优势
- 代码可靠性
- 易于测试
- 组合开发
- 注重 FP 的编程语言
- Haskell
- F#
- Clojure(from Lisp)
- ClojureScript(from JavaScript)
# 学习 函数式编程
# 概览
- 耗时:
- 从入门到理解基本概念,大约 10~20 小时
- Functional JavaScript 代码训练,大约 4~12 小时
- 系统学习函数式架构和模式,至少 80 小时
- 难点:
- 新的编程思维方式
- 数学和 FP 高等概念
- 工具:
# 学习路线
- 前置学习
- 计算机科学入门
- JavaScript
- (TypeScript)
- (单元测试 Jest)
- 学习函数式编程
- 观看资料中的视频部分,理解函数式编程的核心原则
- 练习 JS 中自带的
map
/filter
/reducer
- 学习使用包含轻度 FP 思想的库(Ramda,Redux 等)
- 实战
- 在业务中编写纯函数,处理副作用
- 将已有的逻辑以 FP 方式重新实现
- 进阶
- 系统地 FP 概念代码训练(如 Monad 等)
- 学习 Haskell/F#/Clojure
- 学习 FP 架构设计和设计模式
- 迷思
- 如何更好地管理副作用
- 专为 FP 设计的语言有相应优化(如内置 Immutable),如何处理 JS 中的 FP 性能
# 资料
# 代码训练
- Functional Programming Jargon (opens new window)
- Functional programming design patterns by Scott Wlaschin (opens new window)
# GitHub
- Awesome Functional Programming (opens new window):Awesome FP
- Mostly Adequate Guide (opens new window):JS 函数式编程指南(英文版)
- JS 函数式编程指南 (opens new window):JS 函数式编程指南(汉化版)
- Functional Programming Jargon (opens new window):FP 术语解释
- fantasy-land (opens new window):代数 + JS
- ramda-fantasy (opens new window):Fantasy Land + Ramda
# 视频
- Object Oriented vs Functional Programming with TypeScript (opens new window):12 分钟,OOP vs FP
- 1: 26 - FP:纯函数、副作用、Immutable、函数作为参数/返回值、高阶函数、无状态
- 4: 19 - OOP:类、构造函数、公有/私有、getter/setter、有状态、方法
- 7: 39 - 继承 vs 组合
- 10: 03 - Mixin
- Computerphile 频道
- Programming Paradigms - Computerphile (opens new window):10 分钟
- 指令式 vs 函数式,Haskell
- Functional Programming & Haskell - Computerphile (opens new window):9 分钟
- 无副作用的好处,FP 在产业界的使用,Haskell 的历史,性能、编译优化、并行,健壮性、测试生成
- Lambda Calculus - Computerphile (opens new window):12 分钟
- Lambda 演算,历史、简介、重要性,邱奇-图灵论题。纯 FP 是无类型的。TRUE、FALSE、Y Combinator
- Essentials: Functional Programming's Y Combinator - Computerphile (opens new window):13 分钟
- Y Combinator,fac、loop、rec
- What is a Monad? - Computerphile (opens new window):22 分钟
- Maybe、Nothing、Just,一些 Haskell,Monad 是 FP 中处理副作用的通用模式
- Programming Paradigms - Computerphile (opens new window):10 分钟
- 讲座
- GOTO 2018 • Functional Programming in 40 Minutes • Russ Olsen (opens new window):42 分钟
- 0: 00 - 介绍,产业历史背景,概述
- 2: 55 - 什么是 FP,FP 不是黑魔法,OOP 概览和可能存在的局限性
- 8: 29 - 什么是编程,不同编程语言中的概念
- 10: 06 - FP 和 OOP 中相同的部分,FP 更相当于是一种组织代码的理念
- 11: 17 - 计科和数学碰到的相同困境:如何组织系统、如何做抽象?
- 13: 48 - 函数,集合,输入输出,map,纯函数,副作用
- 17: 41 - 为什么 FP 使程序更易于编写和理解,新的问题和解决方案,Immutable,可持久化数据结构
- 26: 03 - 如何处理副作用
- 33: 22 - FP 不是万能的,它不能解决人工错误(如边界错误),线程安全
- 37: 43 - 一个 Clojure 项目中的 FP 使用情况
- Why Isn't Functional Programming the Norm? – Richard Feldman (opens new window):46 分钟
- 0: 00 - 概述,语言、范式、风格
- 0: 59 - 现在流行的编程语言,流行的几个原因:
- 2: 05 - 独角兽应用:VisiCalc、Ruby、PHP、C
- 6: 22 - 平台独占:Objective-C、swift、JavaScript、C#
- 10: 22 - 快速替换:优势、熟悉程度、学习曲线、生态系统、代码迁移成本,CoffeeScript、TypeScript、C++、Kotlin
- 13: 14 - 商业营销:Java 的故事
- 16: 15 - 稳步增长:Python
- 17: 45 - 其他原因:语法、产业规模、社区
- 18: 46 - 范式,为什么几乎所有主流语言都是 OOP
- 19: 37 - OO 具有独特特性?封装、继承、对象、方法?
- 21: 13 - Go 支持 OO 但是不支持继承,对象和方法只是结构体和过程的语法糖
- 23: 05 - 模块化编程 vs OO 封装,几乎所有语言都支持模块化
- 24: 37 - OO 演进史:ALGOL、simula、Smalltalk、ObjC、C++
- 32: 34 - 语言的流行基于很多别的原因,而不是 OO 特性的先进性
- 19: 37 - OO 具有独特特性?封装、继承、对象、方法?
- 35: 47 - 风格,FP 是语言特性无关的
- 36: 30 - 为什么 FP 不是主流,其实很多 OO 语言已经支持 FP,事情正在起变化
- 39: 58 - 小结 & 问答
- 42: 04 - FP 没有精确的定义
- 44: 00 - 性能需求应根据使用场景进行权衡
- Learning Functional Programming with JavaScript - Anjana Vakil - JSUnconf (opens new window):30 分钟
- 0: 00 - 开场,在 JS 中使用 FP 的经历
- 1: 50 - 什么是 FP,Why FP JS,JS 中的
this
陷阱 - 4: 42 - 纯函数,副作用,HOC,Map/Reduce/Filter,Immutable
- 16: 12 - 可持久化数据(Persistent Data),结构共享
- 22: 27 - 结语 & 提问
- 24: 54 - 编程范式之争
- 28: 10 - Map
- 提到的资料:
- Anjana Vakil: Immutable data structures for functional JS | JSConf EU (opens new window):26 分钟
- 0: 00 - 开场,FP JS 与不可变数据,个人介绍
- 2: 00 - FP 和 Immutable 简介,结构共享,Trie(字典树)
- 11: 52 - 二进制化,Bitmapped Bector Trie,Hash Array Mapped Trie
- 18: 42 - 小结,JS 库介绍:Mori、Immutable
- Scenic City Summit 2016: Jeremy Fairbank - Functional Programming Basics in ES6 (JavaScript) (opens new window):58 分钟
- 0: 00 - 开场,FP in ES6,个人介绍
- 0: 55 - JS 中的 FP 库,什么是 FP,数学中的函数,Lambda 演算
- 4: 24 - 四个原则:纯函数,声明式;安全性,Immutable;透明性,状态;模块化,组合开发
- 6: 03 - ES6 简介:const,箭头函数、剩余参数、默认参数,解构,
Object.assign
,class - 12: 56 - 纯函数 vs 非纯函数,副作用,引用透明,非纯函数难以测试,隐藏的状态就是不确定的状态
- 17: 11 - 指令式 vs 声明式,
- 19: 49 - Immutable,避免 Immutable 的技术方法,
Object.freeze
- 优势:数据安全、UNDO/REDO、显式数据流、内存使用、并发安全
- 劣势:代码冗余,更多对象创建、更多垃圾回收、内存使用
- 26: 28 - 函数一等公民,别名,作为参数,闭包,作为返回值,HOC
- 31: 16 - 偏应用,partial,柯里化,闭包(数学),compose
- 45: 22 - 递归,Stack Overflow,尾调用优化
- 56: 00:资源
- 书:Mostly Adequate Guide (opens new window)
- 语言:Elm、ClojureScript、PureScript(emmm…这是 2016 年的视频…so…)
- 库:Lodash、Ramda、RxJS、Bacon.js、Immutable.js
- 框架:React、Redux
- Functional programming design patterns by Scott Wlaschin (opens new window):65 分钟
- 0: 00 - 前言,FP 设计模式,写 OO 和 FP 的经历,F#
- 3: 43 - OO 模式/原则
- 4: 50 - FP 模式:FP 核心原则、函数作为参数、Monad、Map、Monoid
- 6: 04 - FP 核心原则:函数是实体、组合、类型/而不是类。组合是分形的,类型是可组合的,完整度(Totality),静态类型
- 18: 16 - 函数作为参数:DIY 原则,函数类型作为接口,策略模式,装饰器,单参数
- 27: 49 - 偏应用,依赖注入
- 32: 46 - 好莱坞原则/Continuous,利用偏函数将错误处理外置化
- 36: 10 - 回调函数,链式化
- 38: 42 - Monad,Chaining Continuous
- 41: 35 - Bind,Monadic Bind,Bind 错误处理模式
- 44: 52 - Map,Option,Lift,Functor
- 49: 30 - Monoid,数学性质:闭包(数学);结合律,并行化;单位元,半群,MapReduce,Homomorphism,Endomorphism
- 64: 01 - Monad and Monoid
- Functional architecture - The pits of success - Mark Seemann (opens new window):60 分钟
- 0: 00 - 前言,函数式架构,维持成本 vs 稳定平衡,F#、Haskell,Hindley–Milner 类型系统
- 7: 37 - 稳定平衡的其中三个方面:接口和适配器;服务、实体、值对象;可测试性
- 8: 17 - Hexagonal architecture,OOP 和 FP 对相同架构的实施差异,最大化纯函数的作用
- 16: 50 - Haskell 例子:酒店登记,副作用外置化,编译器限制
- 33: 14 - 服务和数据,OO 的行为膨胀和不可维护性问题,实体、值对象、服务分割
- 41: 07 - FP 中的情况,数据、函数
- 43: 36 - 可测试性,Test-induced Damage,代码噪音,代码隔离
- 8: 17 - Hexagonal architecture,OOP 和 FP 对相同架构的实施差异,最大化纯函数的作用
- 58: 17 - 小结
- 视频中提到的书:
- Brian Lonsdorf - Oh Composable World! (opens new window):28 分钟
- 0: 40 - 组合编程,chain、pipeline、控制流
- 3: 51 - 编程中的反数学范式,范畴论,组合
- 7: 09 - 声明:Box
- 10: 39 - 循环:map/filter/reduce
- 11: 29 - 回调、副作用:lazy Promise
- 14: 51 - 分支、错误处理:Either
- 17: 58 - 风格
- 20: 24 - 在 React 中的尝试
- 26: 18 - 小结,映射 = 组合 = 程序结构
- GOTO 2018 • Functional Programming in 40 Minutes • Russ Olsen (opens new window):42 分钟
# 文章
- 雾雨魔法店 - 知乎专栏
- 阮一峰
- 掘金翻译计划(未成系列的文章)
- Underscore/Lodash/Ramda
- 杂项
# 系列文章
- A GENTLE INTRODUCTION TO FUNCTIONAL JAVASCRIPT
- [译] 准备充分了嘛就想学函数式编程?
- Monads
- 组合软件(译文)
- 组合软件:书 (opens new window)
- 组合软件:简介 (opens new window)
- 不变性之道 (opens new window)
- 函数式编程的兴衰 (opens new window)
- 为什么用 JavaScript 学习函数式编程 (opens new window)
- 掌握 JavaScript 面试:什么是纯函数? (opens new window)
- 掌握 JavaScript 面试:什么是函数式编程 (opens new window)
- 函数式程序员的 JavaScript 简介 (opens new window)
- 高阶函数 (opens new window)
- 柯里化与函数组合 (opens new window)
- 抽象与组合 (opens new window)
- 抽象数据类型与软件危机 (opens new window)
- Functor 与 Category (opens new window)
- JavaScript 让 Monad 更简单 (opens new window)
- 被遗忘的面向对象编程史 (opens new window)
- 对象组合中隐藏的宝藏 (opens new window)
- 用 ES6+ 写 JavaScript 工厂函数 (opens new window)
- 函数式 Mixin (opens new window)
- 为什么 class 更难以进行组合 (opens new window)
- 用函数进行数据类型的组合 (opens new window)
- Lenses:可组合函数式编程的 Getter 和 Setter (opens new window)
- Transducers:JavaScript 中高效的数据处理 Pipeline (opens new window)
- JavaScript 编码风格的原则 (opens new window)
- 模拟是一种代码异味 (opens new window)
- 成书后未收录的章节
- 组合软件:书 (opens new window)
# 函数式编程 知识体系
# 前端中常见的 FP 概念
From Functional Programming Jargon (opens new window)
- Higher-Order Functions (HOF):高阶函数
- Closure:闭包(数学集合中的概念)
- Currying:柯里化
- Function Composition:函数组合
- Pure Function:纯函数
- Side effects:副作用
- Point-Free Style:隐式参数
- Functor:函子
- Lambda Calculus:Lambda 演算
- Lazy evaluation:惰性求值
# Ramda 中有关 FP 概念的 API
- partial
- curry
- lift
- compose/pipe
# FP 基本原则
- 纯函数
- 引用透明(输入输出可控)
- 无副作用
- 不要硬编码
- 面向接口开发(静态类型)
- 代码设计
- 数据和方法
- 行为外置化(OO 方法 => 函数传参)
- 函数
- 副作用外置化(如 error handler、callback 通过传参实现)
- 数据和方法
- 特性
- 不使用 this
- 代码封装
- 模块化
# 函数式编程 典型代码
# 关于副作用
FP:
import { curry } from 'ramda';
let two = 2;
// * 纯函数,柯里化
const add = curry((a, b) => a + b);
const add2 = add(two); // => fn {}
const result1 = add2(3); // => 5
two = 2.1;
const result2 = add2(3); // => 5
2
3
4
5
6
7
8
9
10
11
非 FP:
let two = 2;
// * 副作用(引入了外部变量,导致结果不可预期)
const add2 = (b) => two + b;
const result1 = add2(3); // => 5
two = 2.1;
const result2 = add2(3); // => 5.1
2
3
4
5
6
7
8
# 函数式编程 相关
# FP 在 JS 中的应用
虽然这些库在 JS 中仅仅采用了 FP 的风格,
或仅仅只实施了 FP 理念中的极小一部分。
但无论如何,FP 在现实开发中的确占有一席之地。
# RxJS
RxJS v6 取消了 v5 版链式调用写法,
转而使用 pipe 和纯函数进行实现。
HowTo: Convert to pipe syntax - RxJS v5.x to v6 Update Guide (opens new window)
// * v5
const v5$ = Observable.interval(500)
.filter((e) => e % 2 === 0)
.map((e) => e * 10);
// * v6
const v6$ = interval(500).pipe(
filter((e) => e % 2 === 0),
map((e) => e * 10),
);
2
3
4
5
6
7
8
9
10
# React Hooks
React Hooks 系列函数虽然在内部实现机制上有副作用,
(通过切换 ReactCurrentDispatcher
,以及在内部保存数据)
但在语法上相比之前 class 的写法已经比较有 FP 的味道了。
const App = () => {
const [value, setState] = useState(0);
const count = () => setState((value) => value + 1);
return <button onClick={count}> {value} </button>;
};
2
3
4
5