javascript函数式编程二(1-1-2)

前面说了函数式编程的一些基本概念,现在来说下函子

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来解决。

单子的使用场景可以 参考
函数式编程

本文内容 摘抄于 拉钩大前端训练营

阅读 118

推荐阅读

世界核平

0 人关注
29 篇文章
专栏主页