函数式编程(四)函子
一、什么是函子
函子其实就是一个特殊的容器,这个容器中包含值和值的变形关系(函数)。函子可以通过一个普通的对象来实现,该对象具有map方法,map方法可以运行一个函数对值进行处理(变形关系)。
class Container {
static of(value){
// 创建一个静态方法方便函子创建
return new Container(value)
}
// 创建函子的时候,函子内部要有一个值
constructor(value) {
this._value = value
}
map(fn){
// 返回一个新的函子
return Container.of(fn(this._value))
}
}
let r = Container.of(5)
.map(x => x + 2)
.map(x => x * x)
console.log(r);
二、MayBe函子
在创建函子的时候可能会传入 null 或者 undefined
// 例如:
Container.of(null).map(x => x.toUpperCase()) // 报错了
// 所以我们需要解决值可能为null的情况
// 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;
}
}
const s = MayBe.of(undefined)
.map(x => x.toUpperCase())
console.log(s); // {_value: null}
// 如果在多次调用map方法的时候,某一步使其变成了null,我们是不太明确的
// 创建一个Fail函子专门用来提示异常
class Fail{
static of(value){
return new Fail(value)
}
constructor(value){
this._value = value
}
map(fn){
return this;
}
}
// 将MayBe函子进行修改
class MayBe2{
static of(value){
return new MayBe(value)
}
constructor(value){
this._value = value
}
map(fn){
// 如果执行fn后结果为null或者undefined,将fn
return this.isNothing()? MayBe2.of(null) : (fn(this._value)?MayBe2.of(fn(this._value)):Fail.of(fn.toString()))
}
isNothing(){
return this._value === null || this._value === undefined;
}
}
const t = MayBe.of('hello world')
.map(x => x.toUpperCase())
.map(x => null)
.map(x => x.split(' '))
console.log(t) // Fail { _value: 'x => 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(str){
try{
return Right.of(JSON.parse(str))
} catch(e) {
return Left.of({error: e.message})
}
}
// let r = parseJSON('{name:jake}')
// console.log(r);// 打印结果:Left { _value: { error: 'Unexpected token n in JSON at position 1' } }
let r = parseJSON('{"name":"jake"}').map(x => x.name.toUpperCase())
console.log(r);// 打印结果:Right { _value: { name: 'JAKE' } }
四、IO函子
IO函子与其他函子不一样的是,IO函子的值是一个函数,该函数的返回值就是我们传入的参数。
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))
}
}
const f = IO.of(100)
console.log(f._value()); // 100
IO函子有一些问题需要我们了解以及处理。
// 以下代码是运行在 node 环境下的
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))
}
}
const obj = {
name:"憨批",
age: 18,
gender: 1
}
// getData函数实现获取obj数据
let getData = function(data){
return IO.of(data)
}
// getValues函数获取obj的所有属性值
let getValues = function(data){
console.log("getValues", data);
// 我的本意是:return IO.of(Object.values(data))
// 但实际上,getData返回的是一个IO函数,需要执行其中的_value()方法,才能拿到obj数据
// 如果这样做了就体现不出来IO函子的问题,所以直接返回IO.of(data)
return IO.of(data)
}
let fn = fp.flowRight(getValues, getData)
let result = fn(obj)
console.log(result._value()._value());// 需要调用两次 _value() 才能打印出数据
// 这就是IO函子嵌套的问题
为了解决IO函子的嵌套问题,我们需要用到monad函子,monad函子其实就是在函子中新增了flatMap和join方法。
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(){ // join的作用就是调用 _value
return this._value()
}
flatMap(fn){ // flatMap的作用就是同时去调用map 和join
return this.map(fn).join()
}
}
const obj = {
name:"憨批",
age: 18,
gender: 1
}
// 假设obj是接口返回的数据,一个函数实现获取obj对象数据,一个函数获取obj的所有属性值
let getData = function(data){
return IO.of(data)
}
let getValues = function(data){
return IO.of(Object.values(data))
}
let res = getData(obj)
.flatMap(getValues)// 当我们要合并的这个函数,它返回的值是一个函子,就用flatMap
.join()
console.log(res); // ['憨批', 18, 1]