学习记录-函数式编程-函子

286 阅读3分钟

函子

为什么要学函子

  • 将函数式编程中的副作用控制在可控的范围内、异常处理、异步操作等

什么是函子

  • 容器:包含值与值之间的变形关系(函数)
  • 函子:特殊的容器,通过一个对象来实现,包含map方法,该方法可以运行一个函数(变形关系)对值进行处理
class Container{

// 静态方法of,代替new方法生成一个对象
    static of(value){
        return new Container(value)
    }

// 构造函数
    constructor(value){    
        this._value = value
    }

// map方法 接收一个函数(变形关系),对值进行处理,返回一个新的Container对象/将容器中的每一个值映射到另一个新的容器
    map(fn){
        return Container.of(fn(this._value))
    }
}

// let r = Container.of(3).map(x => x + 2).map(x => x * x)

let r = Container.of(3)

console.log(r)

总结:

  • 函数式编程不直接操作值,而是由函子完成

  • 函子就是实现了map契约的对象

  • 可以将函子想想成一个盒子,盒子内封装了一个值

  • 想要处理值,就要通过函子的map方法传入一个处理值的函数(纯函数),由这个函数对值进行处理

  • 最终map方法返回一个包含新值的函子

在编程过程中可能会遇到很多错误,需要对这些错误做出相应处理

maybe函子

maybe函子可以对外部传入空值做出处理

class Maybe{

    static of(value){
        return new Maybe(value)
    }

    constructor(value){
        this._value = value;
    }

// 传入的值是空值的话,直接返回值为null的函子
    map(fn){
        return this.isNothing() ? Maybe.of(this._value) : Maybe.of(fn(this._value))
    }

    isNothing(){
        return this._value === null || this._value === undefined;
    }
}

// let m = Maybe.of(null).map(x => x.toUpperCase());

let m = Maybe.of('world').map(x => x.toUpperCase());

console.log(m);

在Maybe函子中很难判断到底是哪一步出现了空值异常

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 p = parseJson('{"name": "functor"}').map(x => x.name.toUpperCase());

let p = parseJson('{{"name": "functor"}').map(x => x.name.toUpperCase());

console.log(p);

IO函子

  • IO函子中的_value是一个函数,将函数作为值来处理

  • IO函子可以将不纯的操作存储到_value中,延迟执行这个不纯的操作(惰性执行),使当前的操作纯,

  • 把不纯的操作交给调用者来处理


const fp = require('lodash/fp');

class IO{

    static of(x){
        return new IO(function(){
            return x;
        })
    }

    constructor(fn){
        this._value = fn;
    }

    map(fn){
// 把当前的value和传入的fn组合成为一个函数
        return new IO(fp.flowRight(fn, this._value));
    }
}

// let io = IO.of(process).map(x => x.execPath);

// console.log(io._value());

Task函子 异步执行

  • folktale是一个标准的函数式编程库

  • 与lodash、ramda不同的是,他没有提供很多功能函数

  • 只提供了一些函数式处理的操作,比如compose、curry等,一些函子task either maybe等

const { compose, curry , find} = require('folktale/core/lambda');

const { task } = require('folktale/concurrency/task')

const fs = require('fs')

// folktale的curry函数

// 接收两个参数,一个是传入函数的参数个数,另一个是传入的函数

let f = curry(2, (x, y) => {
    return x + y;
})

// let f1 = f(2);

// console.log(f1(1));

// folktale的函数组合

let f2 = compose(fp.toUpper, fp.map(fp.first));

// console.log(f2(['one', 'two']))
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(x => x.split('\n'))
    .map(fp.find(x => x.includes('name1')))
    .run()
    .listen({
        onRejected: err => {
            console.log(err)
        },
        onResolved: value => {
            console.log(value)
        }
    })

pointed函子

  • pointed函子是实现了of静态方法的函子

  • of方法是为了避免使用new来创建对象,更深层次的含义是of将值放到上下文中,即交给map来处理值

monad 单子

  • monad是可以变扁的pointed函子

  • 如果一个函子具有join和of两种方法, 并且遵守一些定律就是monad函子

class IOmonad{

    static of(x){
        return new IOmonad(function(){
            return x;
        })
    }

    constructor(fn){
        this._value = fn;
    }

    map(fn){
        return new IOmonad(fp.flowRight(fn, this._value))
    }

    join(){
        return this._value();
    }

    flatMap(fn){
        return this.map(fn).join()
    }
}