天天看点

进阶学习4:函数式编程FP——函子Functor、MayBe函子、Either函子、IO函子、Folktale、Pointer函子、Monad六、函子Functor

目录

六、函子Functor

1.函子的概念

2.使用代码演示函子

总结

3.MayBe函子

4.Either函子

5.IO函子(Input输入Ouput输出 函子)

6.Folktale

安装方法

基本使用​

Task函子

7.Pointer 函子

8.IO函子问题

9.Monad(单子)

六、函子Functor

1.函子的概念

学习函子是为了在函数式编程中把副作用控制在可控的范围内、异常处理、异步操作等。

什么是 Functor

容器:包含值和值的变形关系(这个变形关系就是函数)

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

2.使用代码演示函子

我们可以通过.map进行链式调用

//DEMO23
//函子
//函子:是一个特殊的容器,通过一个普通的对象来实现,该对象具有 map 方法,map 方法可以运行一个函数对值进行处理(变形关系)
class Container {
    //构造函数,喊只有一个不对外公布的值,约定下划线的成员为私有的成员
    constructor (value) {
        this._value = value
    }
    //map接收一个函数,并且处理这个值,然后还返回一个新的函子
    map (fn) {
        return new Container(fn(this._value))
    }
}
//map对象返回的永远是个新的函子,在里面保存了值
let result = new Container(5).map(x => x+1).map(x => x * x)
console.log(result)

//但是每次都要用new来创建看起来十分面向对象,而我们需要用函数式编程思想,所以改造一下代码
class Container2 {
    static of (value) {
        return new Container2 (value)
    }
    //构造函数,喊只有一个不对外公布的值,约定下划线的成员为私有的成员
    constructor (value) {
        this._value = value
    }
    //map接收一个函数,并且处理这个值,然后还返回一个新的函子
    map (fn) {
        return Container2.of(fn(this._value))
    }
}

let result2 = Container2.of(5).map(x => x+1).map(x => x * x)
console.log(result2)
           
进阶学习4:函数式编程FP——函子Functor、MayBe函子、Either函子、IO函子、Folktale、Pointer函子、Monad六、函子Functor

总结

  • 函数式编程的运算不直接操作值,而是由函子完成
  • 函子就是一个实现了 map 契约的对象
  • 我们可以把函子想象成一个盒子,这个盒子里封装了一个值
  • 想要处理盒子中的值,我们需要给盒子的 map 方法传递一个处理值的函数(纯函数),由这个函数来对值进行处理
  • 最终 map 方法返回一个包含新值的盒子(函子)

3.MayBe函子

我们在编程的过程中可能会遇到很多错误,需要对这些错误做相应的处理 MayBe函子的作用就是可以对外部的空值情况做处理(控制副作用在允许的范围)

//DEMO24
//MayBe函子
class MayBe {
    static of (value) {
        return new MayBe (value)
    }
    //构造函数,喊只有一个不对外公布的值,约定下划线的成员为私有的成员
    constructor (value) {
        this._value = value
    }
    //map接收一个函数,并且处理这个值,然后还返回一个新的函子
    map (fn) {
        return this.isNothing() ? MayBe.of(null): MayBe.of(fn(this._value))
    }
    //判断是否为null或者undefined
    isNothing() {
        return this._value === null || this._value === undefined
    }
}
let result = MayBe.of('Hello World').map(x => x.toUpperCase())
console.log(result)
let result2 = MayBe.of(null).map(x => x.toUpperCase())
console.log(result2)
//但是如果有一连串的null,maybe函子能够做处理,但是无法具体判断是哪一步map出现的null
let result3 = MayBe.of('Hello World').map(x => x.toUpperCase()).map(x => null).map(x => x.split(' '))
console.log(result3)
           

4.Either函子

Either 两者中的任何一个,类似于 if...else...的处理

异常会让函数变的不纯,Either 函子可以用来做异常处理

//DEMO25
//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))
    }
}

let r1 = Right.of(12).map(x => x + 2)
let r2 = Left.of(12).map(x => x + 2)
console.log(r1)
console.log(r2)

function parseJSON (str) {
    try {
        return Right.of(JSON.parse(str))
    }catch (e) {
        //Left函子记录错误信息
        return Left.of({error : e.message})
    }
}
let r = parseJSON('{name: zs}')
console.log(r)
let r_correct = parseJSON('{"name": "zs"}')
console.log(r_correct)
let r_correct2 = parseJSON('{"name": "zs"}').map(x => x.name.toUpperCase())
console.log(r_correct2)
           

输出结果:

进阶学习4:函数式编程FP——函子Functor、MayBe函子、Either函子、IO函子、Folktale、Pointer函子、Monad六、函子Functor

5.IO函子(Input输入Ouput输出 函子)

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

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 r = IO.of(process).map(p => p.execPath)
console.log(r)
console.log(r._value())
//IO函子内部包装了一些可能是不纯的函数,IO本身当前执行是纯的操作。我们把这个副作用延迟到了只有调用的时候才会发生。
           

6.Folktale

异步任务的实现过于复杂,用 folktale 中的 Task 来演示。

folktale是一个标准的函数式编程库 和 lodash、ramda 不同的是,他没有提供很多功能函数。只提供了一些函数式处理的操作,例如:compose、curry 等,一些函子 Task、Either、 MayBe 等

Folktale官网

https://folktale.origamitower.com/

安装方法

npm install folktale

基本使用

//DEMO27
//folktale 中的 compose、 curry的基本使用
const {compose, curry} = require('folktale/core/lambda')
const {toUpper, first} = require('lodash/fp')

let f = curry(2 ,(x,y)=>{
    return x+y
})
console.log(f(1,2))
console.log(f(1)(2))

let f2 = compose(toUpper, first)
console.log(f2(['one','two']))
           

进阶学习4:函数式编程FP——函子Functor、MayBe函子、Either函子、IO函子、Folktale、Pointer函子、Monad六、函子Functor

Task函子

Folktale Task官方文档

https://folktale.origamitower.com/docs/v2.3.0/migrating/from-data.task/

进阶学习4:函数式编程FP——函子Functor、MayBe函子、Either函子、IO函子、Folktale、Pointer函子、Monad六、函子Functor
//DEMO28
//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(err)

            resolver.resolve(data)
        })
    })
}
//只有调用.run的时候才执行
readFile('package-lock.json')
    .map(split('\n'))
    .map(find(x => x.includes('version')))
    .run().listen({
        //执行失败
        onRejected: err => {
            console.log(err)
        },
        //执行成功
        onResolved: value => {
            console.log(value)
        }
    })
           

没加那两行.map之前返回的是第一个结果,加了之后提取version返回第二个结果 

进阶学习4:函数式编程FP——函子Functor、MayBe函子、Either函子、IO函子、Folktale、Pointer函子、Monad六、函子Functor

7.Pointer 函子

Pointed 函子是实现了 of 静态方法的函子 of 方法是为了避免使用 new 来创建对象,更深层的含义是 of 方法用来把值放到上下文 Context(把值放到容器中,使用 map 来处理值)

其实就是前面我们用到过的带.of的函子都是pointer函子

8.IO函子问题

//DEMO29
//IO函数问题
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))
    }
}
//模拟linux 的cat命令,就是读取文件并打印
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
    })
}

let cat = fp.flowRight(print,readFile)
// IO(IO(x)) 外面的IO是print返回的函子,里面的IO是readFile返回的函子
let r = cat('package-lock.json')._value()._value()
console.log(r)

           

现在的问题是,需要获得print的东西需要些两遍._value(),接下来要解决这个问题,于是会使用到Monad函子

9.Monad(单子)

Monad 函子是可以变扁的 Pointed 函子,IO(IO(x))

一个函子如果具有 join 和 of 两个方法并遵守一些定律就是一个 Monad

//DEMO30
//Monad函子
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))
    }
    join () {
        return this._value()
    }
    flatMap(fn) {
        return this.map(fn).join() //返回的是一个值
    }
}
//模拟linux 的cat命令,就是读取文件并打印
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
    })
}

let r = readFile('package-lock.json').flatMap(print).join()  //再调用的返回的是值用map,返回的是函子用flatMap。因为print返回的是函子,所以用了flatMap
console.log(r)
//把值转换成大写
let r2 = readFile('package-lock.json').map(fp.toUpper).flatMap(print).join() 
console.log(r2)
           

除了上述直接引用外的参考资料:

1.拉勾网 《大前端训练营》课程