简单说,函数式编程(FP)是一种编程范式,比如我们熟知的面向对象编程,也是一种编程方式,和指令式编程不同,它的主要思想是把运算过程尽量写成一系列嵌套的函数调用。
-
函数是第一公民 第一公民的意思是说函数和其他数据类型比如整型、字符串等地位相同,可以把他存放到数组中、当做参数传递、作为函数返回值、赋值给变量等等。
-
单元测试。 在函数式编程中,可以简单认为一切都是
const
,函数不会有副作用,不会修改运行时的任何东西,决定函数的执行结果的唯一因素就是参数,这意味着可以非常容易的进行单元测试 -
易于调试 因为FP不会修改runtime,出现bug时,这些bug是100%必现的,不同于指令式编程,一个bug可能有时重现,有时无法重现,这是因为函数依赖一些外部数据,而这些外部数据又在某些情况下被修改,从而导致这个问题,而在FP中,这种情况不会存在
-
并发执行 不需要任何改动,所有FP程序都是可以并发执行的。由于根本不需要采用锁机制,因此完全不需要担心死锁或是并发竞争的发生。
-
代码简洁
不要修改传入的变量 对象 数组 全局参数等
- 没有副作用
- 易于测试 移植 并发执行 web worker
- 可缓存 可测试
const arr = [1, 2, 3]
arr.splice(0, 1) // 不纯 修改了arr
arr.map(i => i * 2) // 纯
我们应该尽量写纯函数,也可以通过一些手段把一些不纯的函数转成纯函数,常用方法是利用高阶函数
- 函数作为参数传入或作为返回值
- 高阶函数相当普遍,比如基于回调的各种函数 throttle debounce curry compose等等
在函数式编程中,curry随处可见,先看个例子
function add (a, b) {
return a + b
}
const add2 = curry(add)
add2(2)(2) // 4
add2(1, 2) // 3
add2(1)()(2) // 3
这个就是curry 可以只传递给函数一部分参数来调用它,让他返回一个新的函数去处理剩下的参数。等到参数凑齐了,再返回结果
export const curry = (f, arity = f.length, ...args) =>
args.length >= arity ? f(...args) : curry.bind(null, f, arity, ...args)
const compose = function(f, g) {
return function(x) {
return f(g(x))
}
}
f 和 g 都是函数, x 是在它们之间通过管道
传输的值。
export function compose (...fns) {
return function (x) {
return fns.reduceRight((result, fn) => {
return fn(result)
}, x)
}
}
import { compose, filter, sortBy } from 'lodash/fp'
const fromAdmin = filter({ type: 'admin' })
const sorted = sortBy('createdAt')
const unreadByUser = filter({ isReadByUser: false })
const groupedMessages = compose(
sorted,
unreadByUser,
fromAdmin,
)(messages)
满足结合律
compose(f, compose(g, h)) == compose(compose(f, g), h) == compose(f, g, h)
在范畴论中,函子是范畴间的一类映射。函子也可以解释为小范畴范畴内的态射。
function Container (v) {
this.value = v
}
Container.of = function (v) {
return new Container(v)
}
Container 是个只有一个属性的对象,为了能操作这个容器中的属性,我们需要一种函数
Container.prototype.map = function(f){
return Container.of(f(this.value))
}
Container.of(2).map(v => v * 2) // Container(4)
类似这样的Container,称作Functor,Functor 是实现了map函数并遵守一些特定规则的容器类型。
const Maybe = function (x) {
this.value = x
}
Maybe.of = function (x) {
return new Maybe(x)
}
Maybe.prototype.map = function (f) {
return this.value ? Maybe.of(null) : Maybe.of(f(this.value))
}
Maybe.of({name: 'steins'}).map(_.property("age")).map(add(10)) // Maybe(null)
Maybe.of({name: 'steins'}).map(_.property("name")).map(console.log) // Maybe('steins')
const IO = function (f) {
this.value = f
}
IO.of = function (x) {
return new IO(() => x)
}
IO.prototype.map = function (f) {
return new IO(compose(f, this.value))
}
它的特点是接受的参数是一个函数,我们可以用它来实现惰性求值
const win = IO.of(window)
const split = curry((l, str) => str.split(l))
win.map(_.property('location')) // IO {value: f}
win.map(_.property('location')).map(_.property('href')).map(split('/')).value() // ['http:', '', 'lodash.com', 'docs', '4.17.11']
Monod 主要用来处理副作用,因为还没弄懂,有兴趣请自行了解