zoukankan      html  css  js  c++  java
  • 如何用 JavaScript 实现一个数组惰性求值库

    在编程语言理论中,惰性求值(英语:Lazy Evaluation),又译为惰性计算、懒惰求值,也称为传需求调用(call-by-need),是一个计算机编程中的一个概念,它的目的是要最小化计算机要做的工作。它有两个相关而又有区别的含意,可以表示为“延迟求值”和“最小化求值”,除可以得到性能的提升外,惰性计算的最重要的好处是它可以构造一个无限的数据类型。

    看到函数式语言里面的惰性求值,想自己用 JavaScript 写一个最简实现,加深对惰性求值了解。用了两种方法,都不到 80 行实现了基本的数组的惰性求值。

    怎么实现

    惰性求值每次求值的时候并不是返回数值,而是返回一个包含计算参数的求值函数,每次到了要使用值得时候,才会进行计算。

    当有多个惰性操作的时候,构成一个求值函数链,每次求值的时候,每个求值函数都向上一个求值函数求值,返回一个值。最后当计算函数终止的时候,返回一个终止值。

    具体实现

    判断求值函数终止

    每次求值函数都会返回各种数据,所以得使用一个独一无二的值来作为判断流是否完成的标志。刚好 Symbol() 可以创建一个新的 symbol ,它的值与其它任何值皆不相等。

    const over = Symbol();
    
    const isOver = function (_over) {
      return _over === over;
    }

    生成函数 range

    range 函数接受一个起始和终止参数,返回一个求值函数,运行求值函数返回一个值,终止的时候返回终止值。

    const range = function (from, to) {
      let i = from;
      return function () {
        if (i < to) {
          i++
          console.log('range	', i);
          return i
        }
        return over;
      }
    }

    转换函数 map

    接受一个求值函数和处理函数,获取求值函数 flow 中的数据,对数据进行处理,返回一个流。

    const map = function (flow, transform) {
      return function () {
        const data = flow();
        console.log('map	', data);
        return isOver(data) ? data : transform(data);
      }
    }

    过滤函数 filter

    接受一个求值函数,对求值函数 flow 中数据进行过滤,找到符合的数据并且返回。

    const filter = function (flow, condition) {
      return function () {
        while(true) {
          const data = flow();
          if (isOver(data)) {
            return data;
          }
          if(condition(data)) {
            console.log('filter	', data);
            return data;
          }
        }
      }
    }

    中断函数 stop

    接受一个求值函数,当达到某个条件时中断,可以用闭包函数加上 stop 函数接着实现一个 take 函数。

    const stop = function (flow, condition) {
      let _stop = false;
      return function () {
        if (_stop) return over;
        const data = flow();
        if (isOver(data)) {
          return data;
        }
        _stop = condition(data);
        return data;
      }
    }
    
    const take = function(flow, num) {
      let i = 0;
      return stop(flow, (data) => {
        return ++i >= num;
      });
    }

    收集函数 join

    因为返回的都是一个函数,最后得使用一个 join 函数来收集所有的值并且返回一个数组。

    const join = function (flow) {
      const array = [];
      while(true) {
        const data = flow();
        if (isOver(data)) {
          break;
        }
        array.push(data);
      }
      return array;
    }

    测试:

    const nums = join(take(filter(map(range(0, 20), n => n * 10), n => n % 3 === 0), 2));
    console.log(nums);
    
    /* 输出
      range  1
      map    1
      range  2
      map    2
      range  3
      map    3
      filter     30
    
      range  4
      map    4
      range  5
      map    5
      range  6
      map    6
      filter     60
    
      [ 30, 60 ]
    */

    广州VI设计公司https://www.houdianzi.com

    更优雅的实现

    上面使用 函数 + 闭包 实现了惰性求值,但是还是不够优雅,绝大部分代码都放到迭代和判断求值是否完成上面去了。其实 es6 中还有更好方法来实现惰性求值,就是使用 generator,generator 已经帮我们解决了迭代和判断流是否完成,我们就可以专注于逻辑,写出更简洁易懂结构清晰的代码。

    const range = function* (from, to) {
      for(let i = from; i < to; i++) {
        console.log('range	', i);
        yield i;
      }
    }
    
    const map = function* (flow, transform) {
      for(const data of flow) {
        console.log('map	', data);
        yield(transform(data));
      }
    }
    
    const filter = function* (flow, condition) {
      for(const data of flow) {
        console.log('filter	', data);
        if (condition(data)) {
          yield data;
        }
      }
    }
    
    const stop = function*(flow, condition) {
      for(const data of flow) {
        yield data;
        if (condition(data)) {
          break;
        }
      }
    }
    
    const take = function (flow, number) {
      let count = 0;
      const _filter = function (data) {
        count ++
        return count >= number;
      }
      return stop(flow, _filter);
    }

    还得加上链式调用才算是完成了。

    class _Lazy{
      constructor() {
        this.iterator = null;
      }
    
      range(...args) {
        this.iterator = range(...args);
        return this;
      }
    
      map(...args) {
        this.iterator = map(this.iterator, ...args);
        return this;
      }
    
      filter(...args) {
        this.iterator = filter(this.iterator, ...args);
        return this;
      }
    
      take(...args) {
        this.iterator = take(this.iterator, ...args);
        return this;
      }
    
      [Symbol.iterator]() {
        return this.iterator;
      }
    
    }
    
    function lazy () {
      return new _Lazy();
    }

    最后再测试一下:

    const nums = lazy().range(0, 100).map(n => n * 10).filter(n => n % 3 === 0).take(2);
    
    for(let n of nums) {
      console.log('num:	', n, '
    ');
    }
    /* 输出
      range  0
      map    0
      filter     0
      num:   0
    
      range  1
      map    1
      filter     10
      range  2
      map    2
      filter     20
      range  3
      map    3
      filter     30
      num:   30
    */

    好了,大功告成。

    总结

    这样我们就完成了一个最简的数组惰性求值的库,这里只是简单实现了惰性求值,要放到工程中还需要添加很多细节。因为代码不过 80 行,可以很清楚的了解惰性求值原理,还能加深对生成器的理解。

  • 相关阅读:
    IDEA 2020.1 使用eclipse快捷键
    IDEA 2020.1 配置自定义Maven
    Maven 下载、安装、设置国内镜像
    IDEA 2020.1 下载、安装、激活
    MySQL 5.5/5.7 zip版 下载、安装、卸载、报错
    JDK8 下载、安装、配置环境变量
    如何在虚拟机VM15中安装W10
    虚拟机的安装,VMware-workstation-full-15.5.1-15018445
    为什么要买云服务器
    输入子系统实现的按键驱动
  • 原文地址:https://www.cnblogs.com/qianxiaox/p/14017475.html
Copyright © 2011-2022 走看看