进阶学习4:函数式编程FP——函子FunctorMayBe函子Either函子IO函子FolktalePointer函子Monad
Posted JIZQAQ
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了进阶学习4:函数式编程FP——函子FunctorMayBe函子Either函子IO函子FolktalePointer函子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)
总结
- 函数式编程的运算不直接操作值,而是由函子完成
- 函子就是一个实现了 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)
输出结果:
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官网
安装方法
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']))
Task函子
Folktale Task官方文档
https://folktale.origamitower.com/docs/v2.3.0/migrating/from-data.task/
//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返回第二个结果
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)
以上是关于进阶学习4:函数式编程FP——函子FunctorMayBe函子Either函子IO函子FolktalePointer函子Monad的主要内容,如果未能解决你的问题,请参考以下文章