美文网首页大前端
JavaScript 深度剖析---03函数式编程范式

JavaScript 深度剖析---03函数式编程范式

作者: 丽__ | 来源:发表于2021-04-22 16:47 被阅读0次
functor(涵子)

函数不仅可以用于同一个范畴之中值的转换,还可以用于将一个范畴转成另一个范畴。这就涉及到了函子(Functor)。

  • 为什么要学函子
    到目前为止已经已经学习了函数式编程的一些基础,但是我们还没有演示在函数式编程中如何把副作用
    控制在可控的范围内、异常处理、异步操作等。

  • 函子的概念
    函子是函数式编程里面最重要的数据类型,也是基本的运算单位和功能单位。
    它首先是一种范畴,也就是说,是一个容器,包含了值和变形关系。比较特殊的是,它的变形关系可以依次作用于每一个值,将当前容器变形成另一个容器。

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

  • 函子的代码实现
    任何具有map方法的数据结构,都可以当作函子的实现。

// // functor  函子
// class Container {
//     constructor(value) {
//         this._value = value
//     }
//     map(fn) {
//         return new Container(fn(this._value))
//     }
// }


// let r = new Container(5).map(x => x + 1).map(x => x * x)
// console.log(r);

上面代码中,Container 是一个函子,它的map方法接受函数f作为参数,然后返回一个新的函子,里面包含的值是被f处理过的(f(this.val))
一般约定,函子的标志就是容器具有map方法。该方法将容器里面的每一个值,映射到另一个容器。

  • of 方法
    可能注意到了,上面生成新的函子的时候,用了new命令。这实在太不像函数式编程了,因为new命令是面向对象编程的标志。

函数式编程一般约定,函子有一个of方法,用来生成新的容器。

下面就用of方法替换掉new。

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 + 1).map(x => x * x)
console.log(r);

这就更像函数式编程了。

  • 总结
    • 函数式编程的运算不直接操作,而是由函子完成
    • 函子就是一个实现了map契约的对象
    • 我们把函子想象成一个盒子,这个盒子里封装了一个值
    • 想要处理盒子中的值,我们需要给盒子的map方法传递一个处理值的函数(纯函数),由这个函数来对值进行处理
    • 最终map方法返回一个包含新值的盒子(函子)
MayBe(函子)
  • 函子接受各种函数,处理容器内部的值。这里就有一个问题,容器内部的值可能是一个空值(比如null),而外部函数未必有处理空值的机制,如果传入空值,很可能就会出错。
  • 我们在编程的过程中可能会遇到很多错误,需要对这些错误做相对应的处理
  • MayBe 函子的作用就是可以对外部的空值情况做处理(控制副作用在允许的范围)
Container.of(null).map(function (s) {
  return s.toUpperCase();
});
// TypeError

上面代码中,函子里面的值是null,结果小写变成大写的时候就出错了。

class MayBe {
    static of (value) {
        return new MayBe(value)
    }
    constructor(value) {
        this._value = value
    }
    // 如果对空值变形的话直接返回 值为 null 的函子
    map(fn) {
        return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this._value))
    }
    isNothing() {
        return this._value === null || this._value === undefined
    }
}

// 传入具体值 
MayBe.of('Hello World').map(x => x.toUpperCase())
// 传入 null 的情况 
MayBe.of(null).map(x => x.toUpperCase())
// => MayBe { _value: null }

Maybe 函子就是为了解决这一类问题而设计的。简单说,它的map方法里面设置了空值检查。

// 在 MayBe 函子中,我们很难确认是哪一步产生的空值问题,如下例:

MayBe.of('hello world').map(x => x.toUpperCase()).map(x => null).map(x => x.split(' ')) // => MayBe { _value: null }
//因此引入了Either函子
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))
   }
}
  • Either 用来处理异常
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 () {
        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(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异步执行
    • olktale(2.3.2) 2.x 中的 Task 和 1.0 中的 Task 区别很大,1.0 中的用法接近近我们现在演示的函子
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.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 来处理值)
image.png image.png
class Container { 
    static of (value) {
       return new Container(value) }
      …… 
  }
Contanier.of(2) .map(x => x + 5)
Monad(单子)
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
  })
}

let cat = fp.flowRight(print,readFile)
let r = cat('package.json')._value()._value()

console.log(r);

相关文章

网友评论

    本文标题:JavaScript 深度剖析---03函数式编程范式

    本文链接:https://www.haomeiwen.com/subject/pnknlltx.html