前面说了函数式编程的一些基本概念,现在来说下函子
Functor
- 容器:包含值和值的变形关系(这个变形关系就是函数)
- 函子:是一个特殊容器,通过一个普通对象来实现,该对象具有map方法,map方法可以运行一个函数对值进行处理(变形关系,函数)
Functor函子
一个容器包裹一个值
class Container{
static of(value){
//of静态方法,可以省略new关键字创建对象,函数式编程把这些操作封装起来
return new Container(value);
}
constructor(value){
this._value = value;
}
map(fn){
return Container.of(fn(this._value))
}
}
//测试
Container.of(3).map(x=>x+2).map(x=>x*x)
- 函数式编程的运算不直接操作值,而是由函子完成。
- 函子就是一个实现了map方法的契约对象。
- 我们可以把函子想象成一个盒子,盒子里封装了一个值。
- 想要处理盒子里的值,我们需要给盒子的map方法传递一个参数(纯函数),由这个函数来对这个值进行处理。
- 最终map方法返回一个包含新值的盒子(函子)
MayBe函子
这个函子的作用是可以对外部空值情况做处理(控制副作用在允许的范围中)
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
}
}
MayBe.of('hello word').map(x=>x.toUpperCase())
MayBe.of(null).map(x=>x.toUpperCase())//null
//在MayBe函子中,我们很难判断那一步出现了null
MayBe.of('hello word').map(x=>x.toUpperCase()).map(x=>null).map(x=>x.split(' '))//null
Either函子
Either两者中的任何一个,类似if..else的处理
异常会让函数变得不纯,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(json) {
try {
return Right.of(JSON.parse(json));
} catch (e) {
return Left.of({ error: e.message});
}
}
let r = parseJSON('{ "name": "zs" }') .map(x => x.name.toUpperCase())
console.log(r)
IO函子
- io函子中的_value是一个函数,这里是把值作为函数来处理
- io函子可以把不纯的动作储存搭配_value中,延迟执行这个不纯的操作(惰性执行),包装当前操作
- 把不纯的操作交给调用者来处理
const fp = require('lodash/fp');
class IO{
static of(x){
return new IO(function(x){
return x;
})
}
constructor(fn){
this._value=fn;
}
map(fn){
return new IO(fp.flowRight(fn,this._value))
}
}
let io = IO.of(process).map(p=>p.execPath)
console.log(io._value())
Task异步执行
- 异步任务的实现过于复杂,使用folktale中的task来演示
- folktale一个标准的函数式编程的库
- 和lodash,ramda不同的是,他没有提供很多函数,只提供了一些函数式处理的操作。例如:compose,curry,等,一些函子Task,Either,MayBe等,详细信息可以关注下官方文档
const { compose, curry } = require('folktale/core/lambda')
const { toUpper, first } = require('lodash/fp')
//第一个参数是传入函数的参数个数
let f = curry(2, function (x, y) { console.log(x + y) })
f(3, 4)
f(3)(4)
// 函数组合
let f = compose(toUpper, first)
f(['one', 'two'])
Task异步(2.3.3版本)
const { task } = require('folktale/concurrency/task')
function readFile(filename) { //读取文件操作
return task(resolver => {
fs.readFile(filename, 'utf-8', (err, data) => {
if (err) resolver.reject(err)
resolver.resolve(data)
})
})
}
//调用run执行
readFile('package.json')
.map(split('\n'))
.map(find(x=>x.includes('version')))
.run()
.listen({
onRejected:err=>{
console.log(err)
},
onResolved:value=>{
console.log(value)
}
})
Pointed函子
- Pointed函子是实现了of静态方法的函子,就是最开始写的函子。
- of方法是为了避免使用new来创建对象,更深层的含义是of方法来把值放到上下文Context(把值放到容器中,使用map来处理)
最后一种Monad单子
在使用io函子的时候,有时候会写出这种代码
const fs = require('fs')
const fp = require('lodash/fp')
let readFile = function (filename) {
return new IO(function() {
return fs.readFileSync(filename, 'utf-8')
})
}
let print = function(x) {
return new IO(function() {
console.log(x)
return x
})
}
// IO(IO(x)) 合并
let cat = fp.flowRight(print, readFile)
// 调用 ,第一次调用返回 print io函子里的函数,第二次是readFile里的
let r = cat('package.json')._value()._value()
console.log(r)
- Monad 函子是可以变扁的 Pointed 函子,IO(IO(x))
- 一个函子如果具有 join 和 of 两个方法并遵守一些定律就是一个 Monad
const fp = require('lodash/fp')
// IO Monad
class IO {
static of (x) {
return new IO(function () { return x })
}
constructor (fn) {
this._value = fn
}
map (fn) {
return new IO(fp.flowRight(fn, this._value))
}
join () {
return this._value()
}
flatMap (fn) {//合并并执行
return this.map(fn).join()
}
}
let r= readFile('package.json').map(fp.toUpper).flatMap(print).join();
实际上flatMap会让flowRight(print,fp.toUpper,readFile),这样执行的,最后一个print返回一个IO函子,里面的value是一个函数,所以最后需要join执行一下
也就是说函数嵌套,由函数组合可以解决。
函子嵌套可以使用 monad函子来解决
对于函子来说,前面返回的是普通值,可以直接使用map来操作, 如果返回的是一个Monad函子,使用flatMap来解决。
单子的使用场景可以 参考
函数式编程
本文内容 摘抄于 拉钩大前端训练营
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。