如何用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\t', i);
      return i
    }
    return over;
  }
}

转换函数 map

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

const map = function (flow, transform) {
  return function () {
    const data = flow();
    console.log('map\t', 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\t', 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 ] */

更优雅的实现

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

const range = function* (from, to) {
  for(let i = from; i < to; i++) {
    console.log('range\t', i);
    yield i;
  }
}

const map = function* (flow, transform) {
  for(const data of flow) {
    console.log('map\t', data);
    yield(transform(data));
  }
}

const filter = function* (flow, condition) {
  for(const data of flow) {
    console.log('filter\t', 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:\t', n, '\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 行,可以很清楚的了解惰性求值原理,还能加深对生成器的理解。

作者:王大帅

来源:51CTO

时间: 2024-10-30 23:39:18

如何用JavaScript实现一个数组惰性求值库的相关文章

c语言 数组-请教如何用c语言去除一个数组中所有值为零的元素,而且这些零元素中有连续排列的?

问题描述 请教如何用c语言去除一个数组中所有值为零的元素,而且这些零元素中有连续排列的? 能否给一个示例程序?感激不尽! 比如以下这个数组中有连续的0元素,如何去除所有的零元素? double a[64]={4.63866e+020,1.456e+027,-7.67487e+017,9.86481e+016,0,0,-3.1101e+014,-9.38282e+010, 1.456e+027,4.60249e+033,-2.3969e+024,3.36857e+023,0,0,-9.64264e

javascript-如何用JavaScript 修改一个带参数的 onclick 的方法???

问题描述 如何用JavaScript 修改一个带参数的 onclick 的方法??? 比如 添加 怎么通过javascript用 document.getElementById("foo").onclick修改onclick所调用的方法,换成另一个带参数的方法 解决方案 document.getElementById('foo').onclick=function(){ show(param); }; 如果感觉有用,请采纳 解决方案二: 重新再写一个,后写的会覆盖前写的

JS编程建议——65:比较函数的惰性求值与非惰性求值

建议65:比较函数的惰性求值与非惰性求值在JavaScript中,使用函数式风格编程时,应该对于表达式有着深刻的理解,并能够主动使用表达式的连续运算来组织代码.1)在运算元中,除了JavaScript默认的数据类型外,函数也作为一个重要的运算元参与运算.2)在运算符中,除了JavaScript的大量预定义运算符外,函数还作为一个重要的运算符进行计算和组织代码.函数作为运算符参与运算,具有非惰性求值特性.非惰性求值行为自然会对整个程序产生一定的负面影响.先看下面这个示例:var a = 2;fun

《Haskell并行与并发编程》——第2章,第2.1节惰性求值和弱首范式

2.1 惰性求值和弱首范式 Haskell并行与并发编程 Haskell是一门惰性语言,即表达式是在其值需要使用时才被求值2.一般来说,不必担心该过程如何发生,只要表达式在需要时求值,不需要时不被求值即可.但是,当在代码中使用了并行编程后,就需要告诉编译器一些程序运行的信息,即某些代码应该并行执行.由于对惰性求值的工作方式有一个直觉的认识将有助于有效地进行并行编程,因此本节将以GHCi作为试验工具,探讨惰性求值的一些基本概念. 下面从非常简单内容的开始:Prelude> let x = 1 +

JavaScript几种数组去掉重复值的方法推荐_javascript技巧

数组去重复是一个常见的需求,我们暂时考虑同类型的数组去重复.主要是理清思路和考虑下性能.以下方法,网上基本都有,这里只是简单地总结一下. 思路: 1.遍历数组,一一比较,比较到相同的就删除后面的 2.遍历数组,一一比较,比较到相同的,跳过前面重复的,不相同的放入新数组 3.任取一个数组元素放入新数组,遍历剩下的数组元素任取一个,与新数组的元素一一比较,如果有不同的,放入新数组. 4.遍历数组,取一个元素,作为对象的属性,判断属性是否存在 1. 删除后面重复的: function ov1(arr)

c语言栈-栈的问题,用栈编写一个算术表达式求值

问题描述 栈的问题,用栈编写一个算术表达式求值 用栈做一个简单的算术运算,我觉得思想没问题,可是编译不过去,上网找了很多资料,觉得是栈的基本操作有问题,初始化有问题,可具体是什么错误我也不是很了解,求大神指教!! #include #include typedef struct { int data[100]; int top1; }SqStack1; typedef struct { char suanfu[100]; int top2; }SqStack2; SqStack1 shuzi;

如何用javascript设置gridview中textbox的值

问题描述 如何用javascript设置gridview中textbox的值,我在网上搜了许久,没有找到答案,请帮忙,谢谢! 解决方案 解决方案二:甭想你看看生成好的DataGrid的ID和Name是什么样子你就知道为什么不行了.解决方案三:gridview中textbox也可以设置ID,页面有了控件有了ID不就可以用javascript设值了...解决方案四:不会吧,顶一顶解决方案五:gridview里的文件框名称一般来说都是固定的格式如[gridviewid]$[控件id]$[行号]还是啥的

javascript 随机抽取数组中一值

从数组 m 中随机取出 n 个值,看代码:  代码如下 复制代码 var m = [1,2,3,4,5,6,7,8,9,'a','b','c','d','e','#']; function randArray(m, len) {     m.sort(function () {         return Math.random() - 0.5;     });     return m.slice(0, len); } console.log(randArray(m, 4)); 从数组 m

如何用JavaScript定义一个类_基础知识

我原来的写法都是这样: function Dog(){ this.name = 'hachi'; } Dog.prototype = { makeNoise:function(){ alert('wangwangwang'); } }; 后来又看到另外一种复杂一点而且看起来好像没有必要的写法: function Dog(){ var privateVariable = 'secret'; var fn = function(){ //... } fn.prototype = { makeNois