Functor函子

作者: 翔子丶 | 来源:发表于2021-01-04 15:36 被阅读0次
    为什么学习函子?

    函子是函数式编程里面最重要的数据类型,也是基本的运算单位和功能单位

    函子作用:控制函数式编程中的副作用

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

    特点:

    • 函数式编程运算不直接操作值,而是由函子完成
    • 函子就是实一个实现map契约的对象
    • 可以把函子想象成一个盒子,这个盒子封装了一个值
    • 想要处理盒子中的值,需要给盒子的map方法传递一个处理值的函数,由函数对值进行处理
    • 最终map方法返回一个包含新值的盒子(函子)
    // 一个容器 包裹一个值
    class Container {
      // of静态方法,可以省略new关键字创建对象 因为new命令是面向对象编程的标志
      static of(value) {
        return new Container(value)
      }
    
      constructor(value) {
        this._value = value
      }
    
      // map方法,传入变形函数,将容器里的每一个值映射到另一个容器
      map(fn) {
        return Container.of(fn(this._value))
      }
    }
    
    let r = Container.of(5)
      .map((x) => x + 2)
      .map((x) => x * x)
    
    console.log(r) // Container { _value: 49 }
    
    // 演示 null undefined 的问题 值如果不小心传入了空值(副作用)
    Container.of(null).map((x) => x.toUpperCase()) // TypeError: Cannot read property 'toUpperCase' of null
    
    MayBe函子
    • 编程过程中需要对遇到的错误进行相应处理
    • MayBe函子的作用是可以对外部的控制情况做处理(控制副作用在允许的范围)
    // MayBe 函子
    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
      }
    }
    
    // 传入具体值
    let r = MayBe.of('Hello World').map((x) => x.toUpperCase())
    console.log(r) // MayBe { _value: 'HELLO WORLD' }
    
    // 传入null的情况
    let r1 = MayBe.of(null).map((x) => x.toUpperCase())
    console.log(r1) // MayBe { _value: null }
    
    // 在MayBe函子中 很难确认是哪一步产生的空值问题
    let r2 = MayBe.of('hello world')
      .map((x) => x.toUpperCase())
      .map((x) => null)
      .map((x) => x.split(' '))
    console.log(r2) // MayBe { _value: null }
    
    Either函子
    • Either两者中的任何一个,类似于if...else...处理
    • 异常会让函数变的不纯,Either函子可以用来做异常处理
    // Either函子内部有Left(左值)和Right(右值) 右值是正常情况下使用 左值是右值不存在时使用
    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 (str) {
        try {
            return Right.of(JSON.parsr(str))
        } catch (e) {
            return Left.iof({ error: e.message })
        }
    }
    let r = parseJSON('{ name: zs }')
    console.log(r) // Left { _value: { error: 'Unexpected token n in JSON at position 2' } }
    let r1 = parseJSON('{ "name": "zs" }')
              .map(x => x.name.toUpperCase())
    console.log(r1) // Right { _value: 'ZS' }
    
    IO函子
    • IO函子中的_value是一个函数,这里是把函数作为值来处理
    • IO函子可以不不纯的动作封装在_value中,延迟执行这个不纯的操作,包装当前操作为纯函数
    • 把不纯的动作交给调用者来处理
    // IO 函子
    const fp = require('lodash/fp')
    
    class IO {
      static of(value) {
        return new IO(function () {
          return value
        })
      }
    
      constructor(fn) {
        this._value = fn
      }
      // 把当前的 value 和 传入的 fn 组合成一个新的函数
      map(fn) {
        return new IO(fp.flowRight(fn, this._value))
      }
    }
    
    let r = IO.of(process).map((p) => p.execPath)
    console.log(r._value())
    
    const fs = require('fs')
    // 读取文件本是不纯的操作 但是readFile确实纯函数 因为总是返回IO函子
    const readFile = (filename) => fs.readFileSync(filename, 'utf-8')
    let r1 = IO.of(readFile('./01-functor.js'))
    console.log(r1._value())
    
    Task异步执行

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

    // 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)
        })
      })
    }
    
    readFile('package.json')
      .map(split('\n'))
      .map(find(x => x.includes('version')))
      .run()
      .listen({
        onRejected: err => {
          console.log(err)
        },
        onResolved: value => {
          console.log(value)
        }
      })
    
    ap函子

    apapplicative(应用)的缩写,凡是部署了ap方法的函子,就是ap函子

    ap函子实现函子的链式操作

    // 一个容器 包裹一个值
    const MayBe = require('./02-maybe')
    class Ap {
      // of静态方法,可以省略new关键字创建对象
      static of(value) {
        return new Ap(value)
      }
      // ap方法的参数不是函数 而是另一个函子
      ap(F) {
        return Ap.of(this._value(F._value))
      }
      constructor(value) {
        this._value = value
      }
    
      // map方法,传入变形函数,将容器里的每一个值映射到另一个容器
      map(fn) {
        return Ap.of(fn(this._value))
      }
    }
    
    function add(x) {
      return function (y) {
        return x + y
      }
    }
    const x = Ap.of(add).ap(MayBe.of(2)).ap(MayBe.of(3))
    console.log(x) // Ap { _value: 5 }
    
    Monad(单子)

    函子之中再包含函子的情况下会出现多层嵌套的函子,如IO(IO(x))

    Monad函子

    • 返回一个单层的函子
    • flatMap方法:与map作用相同,区别是如果生成了嵌套函子,会取出后者内部的值,保证返回永远是单层容器,不会出现嵌套的情况
    • join方法保证了flatMap方法总是返回一个单层的函子
    • 如果同时具有joinof方法并遵守一些定律就是一个Monad
    // IO Monad
    const fs = require('fs')
    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))
      }
    
      join () {
        return this._value()
      }
    
      flatMap (fn) {
        return this.map(fn).join()
      }
    }
    
    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.json')
              // .map(x => x.toUpperCase())
              .map(fp.toUpper)
              .flatMap(print)
              .join()
    console.log(r)
    

    相关文章

      网友评论

        本文标题:Functor函子

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