美文网首页JS
loadsh中的一些纯函数及JavaScript中的函子

loadsh中的一些纯函数及JavaScript中的函子

作者: A_走在冷风中 | 来源:发表于2021-08-24 10:31 被阅读0次

    一.loadsh中常见的纯函数

    主要有 first / last / toUpper / reverse / each / includes / find / findIndex等

    先引入loadsh

    const _ = require("lodash");
    

    定义一个数组

    const array = ["jack", "tom", "jerry", "kate"];
    

    1. lodash.first()

    获取数组中的第一个参数

    console.log(_.first(array)); //jack
    

    2. lodash.last()

    获取数组中的最后一个参数

    console.log(_.last(array)); //kate
    

    3. lodash.toUpper()

    将字符串转为大写

    console.log(_.toUpper(_.first(array))); //JACK
    

    4. lodash.reverse()

    颠倒数组顺序

    console.log(_.reverse(array)); //[ 'kate', 'jerry', 'tom', 'jack' ]
    

    5.lodash.each()

    循环数组

    const r = _.each(array, (item, index) => {
      console.log(item, index);
      //jack 0
      //tom 1
      //jerry 2
      //kate 3
    });
    
    console.log(r); //["jack", "tom", "jerry", "kate"]
    

    6. lodash.includes()

    判断数组中是否包含某个元素,返回true 或 false

    console.log(_.includes(array, "tom")); //true
    console.log(_.includes(array, "rose")); //false
    

    7. lodash.find()

    根据下标查找数组中的某个元素

    console.log(_.find(array, 1)); //jack
    

    8.Loadsh.findIndex()

    用于查找元素首次出现的索引。它与indexOf不同,因为它采用了遍历数组每个元素的谓词函数。当元素存在时,返回元素的下标,元素不存在时,返回-1

    let index = _.findIndex(
      array,
      (item) => {
        return (item = "tom");
      },
      0
    );
    console.log(index); //0
    
    let index = _.findIndex(
      array,
      (item) => {
        return (item = "rose");
      },
      0
    );
    console.log(index); // -1
    

    当元素在某个索引“i”之后被查找时。这里的元素存在于数组中,但输出仍然为-1,因为它存在于索引0中,并且搜索从索引1开始。

    let index = _.findIndex(
      array,
      (item) => {
        return (item = "tom");
      },
      1
    );
    console.log(index); // -1
    

    findIndex() 接收三个参数,第一个参数为要查找的数组,第二个参数为数组遍历后的需要满足的条件,第三个参数为从哪个索引开始查找.
    注:第三个参数可以省略,省略时默认从0开始查找

    二.Functor(函子)

    什么是Functor
    • 容器:包含值和值的变形关系(这个变形关系就是函数)
    • 函子:是一个特殊的容器,通过一个普通的对象来实现,该对象具有map方法,map方法可以运行一个函数,对值进行处理(变形关系)
    案例:
    //定义一个容器
     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 { _value: 36 }
    

    此时,每次调用Container,都要通过new方法来进行调用
    我们可以通过在class中定义static方法来进行改造:

    class Container {
      constructor(value) {
        //私有属性
        this._value = value
      }
      map(fn) {
        return Container.of(fn(this._value))
      }
      //定义静态方法
      static of(value) {
        return new Container(value)
      }
    }
    

    如此一来,我们就可以通过函数调用来调用Container类

    let r = Container.of(5)
      .map((x) => x + 2)
      .map((x) => x * x)
    
    console.log(r) //Container { _value: 49 }
    
    总结:
    • 函数式编程的运算不直接操作值,而是由函子完成
    • 函子就是一个实现了map契约的对象
    • 我们可以把函子想象成一个盒子,这个盒子里封装了一个值
    • 想要处理盒子中的值,我们就需要给盒子的map方法传递一个处理之的函数,由这个函数对值进行
      处理
    • 最终map方法返回一个包含新值的盒子(函子)

    JavaScript中常用的函子

    1.MayBe函子:
    • 我们在变成的过程中可能会遇到很多错误,需要对这个错误做相应的处理

    • MayBe函子的作用就是对外部的空值情况做处理(控制副作用在允许的范围内)

      案例 :
    //定义一个MayBe函子
    class MayBe {
    
      static of(value) {
        return new MayBe(value)
      }
    
      constructor(value) {
        this._value = value
      }
      map(fn) {
        //参数为null时,返回value 为 null
        return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this._value))
      }
      //判断参数是否为null
      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 r2 = MayBe.of(null).map(x => x.toUpperCase())
    console.log(r2) //MayBe { _value: null }
    

    如果map中间有好几步,最后返回null,但是并不知道那一步出现了问题

    let r3 = MayBe.of('hello world')
      .map(x => x.toUpperCase())
      .map(x => null)
      .map(x => x.splice(''))
    
    console.log(r3) //MayBe { _value: null }
    

    为解决这个问题, 需要看下一个函数

    2.Either函子
    • Either 两者中的任何一个,类似于 if..else..的处理
    • 当出现问题的时候,Either函数会给出提示的有效信息,
    • 异常会让函数变的不纯,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 = left.of(12).map(x => x + 2)
    let r2 = Right.of(12).map(x => x + 2)
    console.log(r1) //left { _value: 12 }
    console.log(r2) //left { _value: 14 }
    

    为什么返回的结果会不一样呢,因为left返回的是this(当前对象),并没有调用fn
    那么如何处理异常呢 ?
    我们定义一个字符串转换成对象的函数

    const parseJson = (str) => {
      //对于可能出现错误的环节使用 try catch
      //正确的情况使用Right函子
      try {
        return Right.of(JSON.parse(str))
      } catch (e) {
        //错误的情况使用left函子,并返回错误信息
        return left.of({ err: e.message })
      }
    }
    
    let error = parseJson('{name:zs}')
    console.log(error) //left { _value: { err: 'Unexpected token n in JSON at position 1' } }
    let success = parseJson('{"name":"zs"}')
    console.log(success) //Right { _value: { name: 'zs' } }
    
    console.log(success.map(x => x.name.toUpperCase())) //Right { _value: 'ZS' }
    
    3. IO函子
    • IO就是输入输出,IO函子中 _value 是一个函数,这里是把函数作为值来处理
    • IO 函子可以把不纯的动作存储到_value中,延迟执行这个不纯的操作,把不纯的操作交给调用者处理(甩锅)
    const fp = require('lodash/fp')
    
    class IO {
    
      //of方法快速创建一个值返回一个函数,将来需要的时候再调用函数
      static of(value) {
        return new IO(() => value)
      }
    
      //传入的是一个函数
      constructor(fn) {
        this._value = fn
      }
    
      map(fn) {
        //这里用的是new一个新的构造函数,是为了把当前_value的函数和map传入的fn进行组合成新的函数
        return new IO(fp.flowRight(fn, this._value))
      }
    }
    

    node执行环境可以传入一个process对象(进程)
    调用of的时候把当前取值的过程包装到函数里面,再在需要的时候再获取process

    const R = IO.of(process)
      //map需要传入一个函数,函数需要接受一个参数,这个参数就是of中传递的参数process
      //返回一下process中的execPath属性,即当前执行路径
      .map(p => p.execPath)
    
    console.log(R) //IO { _value: [Function (anonymous)] }
    
    //上面返回的是一个函数,如需获取路径可执行下面的代码
    console.log(R._value()) //C:\Program Files\New Folder\node.exe
    
    4.task函子
    const { task } = require('folktale/concurrency/task')
    const fs = require('fs')
    const { split, find } = require('lodash/fp')
    //读取文件
    function readFile(fileName) {
      //task传递一个函数,参数是resolver
      return task(resolver => {
        //node中读取文件,第一个参数是文件路径,第二个参数是编码方式,第三个参数是回调(错误在前面)
        fs.readFile(fileName, 'utf-8', (err, data) => {
          if (err) {
            resolver.reject(err)
          } else {
            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)
        }
      }) //"version": "1.0.0",
    
    5.monad函子
    • 用来解决io函子多层嵌套问题
      IO函子中的问题:
    const fp = require('lodash/fp')
    const fs = require('fs')
    
    class IO {
      //of方法快速创建一个值返回一个函数,将来需要的时候再调用函数
      static of(value) {
        return new IO(() => value)
      }
    
      //传入的是一个函数
      constructor(fn) {
        this._value = fn
      }
    
      map(fn) {
        //这里用的是new一个新的构造函数,是为了把当前_value的函数和map传入的fn进行组合成新的函数
        return new IO(fp.flowRight(fn, this._value))
      }
    }
    
    //读取文件
    let readFile = (fileName) => {
      return new IO(() => {
        return fs.readFileSync(fileName, 'utf-8')
      })
    }
    
    //打印函数
    let print = (value) => {
      return new IO(() => {
        console.log(value)
        return value
      })
    }
    
    //组合函数,先读文件再打印
    let cat = fp.flowRight(print, readFile)
    let r = cat('package.json')
    //拿到的是嵌套的IO函子 IO(IO())
    console.log(r) //IO { _value: [Function (anonymous)] }
    
    console.log(r._value()._value())
    /**
     * {
      "name": "pd_funxtor",
      "version": "1.0.0",
      "description": "",
      "main": "either.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "keywords": [],
      "author": "",
      "license": "ISC",
      "dependencies": {
        "folktale": "^2.3.2",
        "lodash": "^4.17.21"
      }
     }
     */
    

    上面遇到多个IO函子嵌套的时候,_value就会调用很多次,这样的调用体验很不好,所以进行优化

    什么是Monad函子
    • Monad函子是可以变扁的Pointed函子,用来解决IO函子嵌套问题
    • 一个函子如果具有join 和 of 两个方法并遵守一定的规则,就是Monad函子
    实现一个Monad函子

    实际开发中 不会这么难,主要是知道Monad的实现

    const fp = require('lodash/fp')
    const fs = require('fs')
    
    class IO {
      //of方法快速创建一个值返回一个函数,将来需要的时候再调用函数
      static of(value) {
        return new IO(() => value)
      }
    
      //传入的是一个函数
      constructor(fn) {
        this._value = fn
      }
      map(fn) {
        //这里用的是new一个新的构造函数,是为了把当前_value的函数和map传入的fn进行组合成新的函数
        return new IO(fp.flowRight(fn, this._value))
      }
      join() {
        return this._value()
      }
      flatMap(fn) {
        return this.map(fn).join()
      }
    }
    
    //读取文件
    let readFile = (fileName) => {
      return new IO(() => {
        return fs.readFileSync(fileName, 'utf-8')
      })
    }
    
    //打印函数
    let print = (value) => {
      return new IO(() => {
        console.log(value)
        return value
      })
    }
    
    let r = readFile('package.json')
      .flatMap(print)
      .join()
    console.log(r)
    //执行顺序
    /**
     * readFile读取了文件,然后返回了一个 IO函子
     * 调用flatMap使用readFile返回的IO函子调用的
     * 并且传入了一个print函数
     * 调用flatMap的时候 内部先调用map,当前的print和this._value进行合并,返回一个新的函子
     * flatMap中的map函数执行完,print函数返回的一个IO函子,里面包裹的还是一个IO函子
     * 下面调用join函数,返回内部的this._value
     * 这个this._value就是之前print和this._value的组合函数,调用之后返回的就是print的返回结果
     * 所以flatMap执行完毕之后,返回的就是print函数返回的IO函子
     */
    

    相关文章

      网友评论

        本文标题:loadsh中的一些纯函数及JavaScript中的函子

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