函子的概念和常见函子介绍

334 阅读4分钟

Functor (函子)

什么是函子Functor

  • 容器:包含值和值的变形关系(变形关系就是函数)
  • 函子:是一个特殊的容器,通过一个普通的对象来实现,该对象具有map方法,map方法可以运行一个函数对值进行处理(变形关系)

Functor函子

// 一个容器,包含一个值
class Container {
    // of 静态方法,可以省略 new 关键字创建对象
    static of(value){
		return new Container(value)
    }
    
    constructor (value) {
        this._value = value;
    }
    
    // map方法,传入变形关系(一个函数),将容器里的每一个值映射到一个新的容器中
    map(fn) {
		return Container.of(fn(this._value))
    }
}

// 测试
Container.of(3)
	.map(x => x + 2)
	.map(x => x * 3)
  • 总结
    • 函数式编程的运算不直接操作值,而是由函子完成
    • 函子就是一个实现了map方法的对象
    • 我们可以把函子想象成一个盒子,这个盒子里封装了一个值
    • 想要处理盒子中的值,我们需要给盒子的map方法传递一个处理值的函数(纯函数),由这个函数来对值进行处理
    • 最终map方法返回一个处理过的值的新函子

Maybe函子

  • 编程过程中可能会遇到许多错误,需要对这些错误做相应的处理
  • Maybe函子的作用就是可以对外部的空值情况做处理(控制副作用在允许的范围内)
// 一个容器,包含一个值
class Container {
    // of 静态方法,可以省略 new 关键字创建对象
    static of(value){
        return new Container(value)
    }
    
    constructor (value) {
        this._value = value;
    }
    
    // map方法,传入变形关系(一个函数),将容器里的每一个值映射到一个新的容器中
    map(fn) {
        return isNothing() ? Container.of(null) : Container.of(fn(this._value))
    }
    
    isNothing(){
        return this._value === null || this._value === undefined;
    }
}

// 测试
Maybe.of('hello world')
    .map(x => x.toUpperCase())
// 传入 null 的情况
Maybe.of(null)
	.map(x => x.toUpperCase())

// 在Maybe函子中,我们是很难确认是哪一步产生的空值问题,如下
Maybe.of('hello world')
    .map(x => x.toUpperCase())
	.map(x => null)
	.map(x => x.split(' '))

Either 函子

  • Either两者中的任何一个,类似于if...else...的处理
  • 异常会让函数变的不纯,Either函子可以用来做异常处理
class Left {
    static of(value){
        return new Container(value)
    }
    constructor (value) {
        this._value = value;
    }
    map(fn) {
        return this
    }
}

class Right {
    static of(value){
        return new Container(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.toUpper } )
console.log(r);

IO函子

  • IO函子中的_value是一个函数,这里是把函数作为值来处理的
  • IO函子可以把不纯的动作存储到_value中,延迟执行这个不纯的操作(惰性执行),把不纯的操作交给调用者来处理
const fp = require('lodash/fp');

class IO {
    static of(value){
        return new Container(function(){
            return value;
        })
    }
    constructor (fn) {
        this._value = fn
    }
    map(fn) {
        // 把当前的value值 和 传入的 fn 组合成一个新的函数
        return IO.of(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 { task } = require('folktable/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(err);
            resolve.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,'uft-8')
    })
}

let print = function(x){
	return new IO(function(){
        return new IO(function(){
            console.log(x);
            return x;
        })
    })
}

// IO(IO(x))
let cat = fp.flowRight( print, readFile );

// 调用
let r = cat('package.json')._value()._value();
console.log(r);
  • Monad 函子是可以变扁的Pointed 函子,IO(IO(x))
  • 一个函子如果具有 join 和 of 两个方法并遵守一些定律,就是一个 Monad 函子
// IO Monad
class IO {
    static of(x){
        return new IO(function(){
            return x
        })
    }
    constructor(fn){
        this._value = value;
    }
    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()