函数式编程
1. 什么是函数式编程
- 与面向对象编程,过程式编程 并列的编程范式(编程式思想)
- 核心思想:把运算过程抽象成函数,在编程的过程中面向函数进行编程
- 最主要特征:函数是第一等公民
- 只有纯的,没有副作用的函数
2. 函数是一等公民
- 函数可以存储在变量中
- 函数可作为返回值
- 函数可作为参数
let arr = [1, 2, 3, 4] const map = (array, fn) => { let results = [] for (let value of array) { results.push(fn(value)) } return results } arr = map(arr, v => v * v) console.log(arr)
3. 高级函数-纯函数
a.概念:
- 纯函数对于相同的输入永远得到相同的输出
- 没有任何的副作用
- 纯函数中的函数指的是数学中的函数
b.好处:
- 可缓存
- 可测试函数
- 可并行处理
-
在多线程环境下并行操作共享的内存数据很有可能会出现意外情况
-
纯函数不需要访问共享的数据,所以在并行环境下可以任意运行纯函数(web worker)
- 副作用:
- 副作用让函数变得不纯,如果函数依赖于外部的状态就无法保证输出相同,就会带来副作用
- 配置文件,数据库,获取用户输入,等所有的外部交互都会产生副作用
function getSum(n1, n2) { return n1 + n2 } console.log(getSum(1, 2)) console.log(getSum(1, 2)) console.log(getSum(1, 2))
4. 高级函数-柯里化Curry
- 柯里化Curry也是高阶函数
- 柯里化Curry内部使用闭包,对函数的参数做了 “缓存”
-
可以把多元函数转换成一元函数,可以组合使用函数产生强大的功能
-
函数的柯里化可以让我们给一个函数传递较少的参数得到一个已经记住了某些固定参数的新函数,接收剩余的参数,返回结果
-
让函数变得更灵活,让函数的颗粒更小
const compose = function (f, g) { return function (x) { return f(g(x)); }; } compose(f, compose(g, h)) // 等同于 compose(compose(f, g), h) // 等同于 compose(f, g, h)
5. 高级函数-函数组合
- 函数可以看做一个处理数据的管道,管道中输入参数x,在管道中对数据处理后得到结果y
- 通过函数组合可以把多个医院函数组合成一个功能更强大的函数
- 函数组合需要满足结合律
- 函数组合默认的执行顺序是从右向左
function compose(t, f, g) { return function (value) { return t(f(g(value))) } } function reverse(array) { return array.reverse() } function first(array) { return array[0] } function toUpper(array) { return array.toUpperCase() } const last = compose(toUpper, first, reverse) console.log(last(['one', 'two', 'there']))
6. 高级函数-管道
8. 函子
a. 概念:
-
容器:包含值和值的变形关系(这个变形关系就是函数)
-
函数式编程的运算不直接操作值,而是由函子完成
-
函子就是一个实现了map契约的对象
-
我们可以把函子想象成一个盒子,这个盒子里封装一个值
-
想要处理盒子中的值,我们需要给盒子的map方法传递一个处理的函数(纯函数),由这个函数对值进行处理
-
最终map方法返回一个包含新值的盒子(函子)
b: 作用:
- 处理纯函数的副作用
- 异常处理
- 异步操作
9. 函子基本使用-Functor
class Container { static of(value) { return new Container(value) } constructor(value) { this._value = value } map(fn) { return Container.of(fn(this._value)) } } let r = Container.of(5).map(x => x + 2).map(x => x * x) console.log(r)
10. 函子空值处理-MayBe
a. 作用:
- 处理外部的空值情况,防止空值的异常
class MayBe { static of(value) { return new MayBe(value) } constructor(value) { this._value = value } map(fn) { return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this._value)) } isNothing() { return this._value === null || this._value === undefined } } // let r = MayBe.of('Hello world').map(x => x.toUpperCase()) let r = MayBe.of(null).map(x => x.toUpperCase()) console.log(r)
11. 函子异常处理-Either
- Either 判断选择两者中的任何一个
class Left {
static of(value) {
return new Left(value)
}
constructor(value) {
this._value = value
}
map(fn) {
return this
}
}
class Right {
static of(value) {
return new Right(value)
}
constructor(value) {
this._value = value
}
map(fn) {
return Right.of(fn(this._value))
}
}
function parseJson(str) {
try {
return Right.of(JSON.parse(str))
} catch (e) {
return Left.of({ error: e.message })
}
}
console.log(parseJson('{name: zhangsan}'))
12. 函子延迟-IO
-
IO函子内部封装的值是一个函数,这里把函数作为值来处理
-
IO函子可以把不纯的操作存储到这个函数中,延迟执行这个不纯的操作(惰性执行),包装当前的操作
-
把不纯的操作交给调用者来处理
var fs = require('fs'); var readFile = function(filename) { return new IO(function() { return fs.readFileSync(filename, 'utf-8'); }); }; var print = function(x) { return new IO(function() { console.log(x); return x; }); } readFile('./user.txt') .flatMap(print)
13. 函子异步处理-Task
// task const fs = require('fs') const { task } = require('folktale/concurrency/task') const { split, find } = require('lodash/fp') function readFile(filename) { return task(resolver => { fs.readFile(filename, 'utf-8', (err, data) => { if (err) resolver.reject() resolver.resolve(data) }) }) } readFile('package.json') .map(split('\n')) .map(find(x => x.includes('version'))) .run() .listen({ onRejected: err => { console.log(err) }, onResolved: value => { console.log(value) } }) console.log()
14. 函子嵌套-Monad
- Monad内部封装的值是一个函数(这个函数返回函子)
- 目的是通过Join方法避免函子嵌套
const fp = require('lodash/fp') const fs = require('fs') class IO { static of(value) { return new IO(function () { return value }) } constructor(fn) { this._value = fn } map(fn) { return new IO(fp.flowRight(fn, this._value)) } } let readFile = function (filename) { return new IO(function () { return fs.readFileSync(filename, 'utf-8') }) } let print = function (x) { return new IO(function (params) { console.log(x) return x }) } let cat = fp.flowRight(print, readFile) let r = cat('package.json') console.log(r._value()._value())
15. 函数式编程的作用
- 让代码可以重用
16. 函数式编程的库
- Lodash---
- Folktale---函子异步处理库
17. 问题总结:
- 函子是只有类可以实现嘛?
- static 的作用?