该系列会有 3 篇文章,分别介绍什么是函数式编程、剖析函数式编程库、以及函数式编程在 React 中的应用,欢迎关注个人 bloghtml
拿泡茶这个事例进行区分命令式编程和声明式编程git
1.烧开水(为第一人称)
2.拿个茶杯
3.放茶叶
4.冲水github
1.给我泡杯茶(为第二人称)编程
举个 demo数组
// 命令式编程 const convert = function(arr) { const result = [] for (let i = 0; i < arr.length; i++) { result[i] = arr[i].toLowerCase() } return result } // 声明式编程 const convert = function(arr) { return arr.map(r => r.toLowerCase()) }
函数式编程是声明式编程的范式。在函数式编程中数据在由纯函数组成的管道中传递。缓存
函数式编程能够用简单如
交换律、结合律、分配律
的数学之法来帮咱们简化代码的实现。闭包
它具备以下一些特性:app
// 反面示例 let a = 0 const add = (b) => a = a + b // 两次 add(1) 结果不一致 // 正确示例 const add = (a, b) => a + b
// 反面示例 const arr = [1, 2] const arrAdd = (value) => { arr.push(value) return arr } arrAdd(3) // [1, 2, 3] arrAdd(3) // [1, 2, 3, 3] // 正面示例 const arr = [1, 2] const arrAdd = (value) => { return arr.concat(value) } arrAdd(3) // [1, 2, 3] arrAdd(3) // [1, 2, 3]
在后记 1 中对数组字符串方法是否对原值有影响做了整理ide
const add = a => b => c => a + b + c add(1)(2)(3)
const add = a => (b, c) => a + b + c add(1)(2, 3)
const add = (x) => x + x const mult = (x) => x * x const addAndMult = (x) => add(mult(x))
以下是一个加法函数:函数式编程
var add = (a, b, c) => a + b + c add(1, 2, 3) // 6
假若有这样一个 curry
函数, 用其包装 add
函数后返回一个新的函数 curryAdd
, 咱们能够将参数 a、b
进行分开传递进行调用。
var curryAdd = curry(add) // 如下输出结果都相同 curryAdd(1, 2, 3) // 6 curryAdd(1, 2)(3) // 6 curryAdd(1)(2)(3) // 6 curryAdd(1)(2, 3) // 6
核心思路: 若传进去的参数个数未达到 curryAdd
的个数,则将参数缓存在闭包变量 lists 中:
function curry(fn, ...args) { const length = fn.length let lists = args || [] let listLen return function (..._args) { lists = [...lists, ..._args] listLen = lists.length if (listLen < length) { const that = lists lists = [] return curry(fn, ...that) } else if (listLen === length) { const that = lists lists = [] return fn.apply(this, that) } } }
如今有 toUpperCase
、reverse
、head
三个函数, 分别以下:
var toUpperCase = (str) => str.toUpperCase() var reverse = (arr) => arr.reverse() var head = (arr) => arr[0]
接着使用它们实现将数组末位元素大写化输出, 能够这样作:
var reverseHeadUpperCase = (arr) => toUpperCase(head(reverse(arr))) reverseHeadUpperCase(['apple', 'banana', 'peach']) // "PEACH"
此时在构建 reverseHeadUpperCase
函数的时候, 必须手动声明传入参数 arr, 是否能提供一个 compose
函数让使用者更加友好的使用呢? 相似以下形式:
var reverseHeadUpperCase = compose(toUpperCase, head, reverse) reverseHeadUpperCase(['apple', 'banana', 'peach']) // "PEACH"
此外 compose
函数符合结合律
, 咱们能够这样子使用:
compose(compose(toUpperCase, head), reverse) compose(toUpperCase, compose(head, reverse))
以上两种写法与 compose(toUpperCase, head, reverse)
的效果彻底相同, 都是依次从右到左执行传参中的函数。
此外 compose
和 map
一块儿使用时也有相关的结合律, 如下两种写法效果相等
compose(map(f), map(g)) map(compose(f, g))
代码精华集中在一行以内, 其为众多开源库(好比 Redux) 所采用。
var compose = (...args) => (initValue) => args.reduceRight((a, c) => c(a), initValue)
范畴论是数学中的一个分支。能够将范畴理解为一个容器, 把原来对值的操做,现转为对容器的操做。以下图:
学习函数式编程就是学习各类函子的过程。
函数式编程中, 函子(Functor)
是实现了 map
函数的容器, 下文中将函子视为范畴,模型可表示以下:
class Functor { constructor(value) { this.value = value } map(fn) { return new Functor(fn(this.value)) } }
可是在函数式编程中, 要避免使用 new
这种面向对象的编程方式, 取而代之对外暴露了一个 of
的接口, 也称为 pointed functor
。
Functor.of = value => new Functor(value)
Maybe 函子
是为了解决 this.value
为 null 的情形, 用法以下:
Maybe.of(null).map(r => r.toUpperCase()) // null Maybe.of('m').map(r => r.toUpperCase()) // Maybe {value: "M"}
实现代码以下:
class Maybe { constructor(value) { this.value = value } map(fn) { return this.value ? new Maybe(fn(this.value)) : null } } Maybe.of = value => new Maybe(value)
Either 函子
是为了对应 if...else...
的语法, 即非左即右
。所以能够将之拆分为 Left
和 Right
两个函子, 它们的用法以下:
Left.of(1).map(r => r + 1) // Left {value: 1} Right.of(1).map(r => r + 1) // Right {value: 2}
Left 函子
实现代码以下:
class Left { constructor(value) { this.value = value } map(fn) { return this } } Left.of = value => new Left(value)
Right 函子
实现代码以下(其实就是上面的 Functor
):
class Right { constructor(value) { this.value = value } map(fn) { return new Right(fn(this.value)) } } Right.of = value => new Right(value)
具体 Either
函数只是对调用 Left 函子
或 Right 函子
做一层筛选, 其接收 f
、g
两个函数以及一个函子(Left or Right
)
var Either = function(f, g, functor) { switch(functor.constructor) { case 'Left': return f(functor.value) case 'Right': return g(functor.value) default: return f(functor.value) } }
使用 demo:
Either((v) => console.log('left', v), (v) => console.log('def', v), left) // left 1 Either((v) => console.log('rigth', v), (v) => console.log('def', v), rigth) // rigth 2
函子会发生嵌套, 好比下面这样:
Functor.of(Functor.of(1)) // Functor { value: Functor { value: 1 } }
Monad 函子
对外暴露了 join
和 flatmap
接口, 调用者从而能够扁平化嵌套的函子。
class Monad { constructor(value) { this.value = value } map(fn) { return new Monad(fn(this.value)) } join() { return this.value } flatmap(fn) { return this.map(fn).join() } } Monad.of = value => new Monad(value)
使用方法:
// join Monad.of(Monad.of(1).join()) // Monad { value: 1 } Monad.of(Monad.of(1)).join() // Monad { value: 1 } // flatmap Monad.of(1).flatmap(r => r + 1) // 2
Monad 函子能够运用在 I/O 这种不纯的操做上将之变为纯函数的操做,目前比较懵懂,往后补充。
var test = [1, 2, 3] var result = test.slice(0, 1) console.log(test) // [1, 2, 3] console.log(result) // [1]
var test = [1, 2, 3] var result = test.concat(4) console.log(test) // [1, 2, 3] console.log(result) // [1, 2, 3, 4]
var test = [1, 2, 3] var result = test.splice(0, 1) console.log(test) // [2, 3] console.log(result) // [1]
var arr = [2, 1, 3, 4] arr.sort((r1, r2) => (r1 - r2)) console.log(arr) // [1, 2, 3, 4]
var test = [1, 2, 3] var result = test.reverse() console.log(test) // [3, 2, 1] console.log(result) // [3, 2, 1]
var test = [1, 2, 3] var result = test.push(4) console.log(test) // [1, 2, 3, 4] console.log(result) // 4
// substr var test = 'abc' var result = test.substr(0, 1) console.log(test) // 'abc' console.log(result) // a // substring var test = 'abc' var result = test.substring(0, 1) console.log(test) // 'abc' console.log(result) // a // slice var test = 'abc' var result = test.slice(0, 1) console.log(test) // 'abc' console.log(result) // a