JavaScript惰性求值的一种实现方法示例


Posted in Javascript onJanuary 11, 2019

前言

在学习 Haskell 时,我遇到了这种写法:

sum (takeWhile (<10000) (filter odd (map (^2) [1..])))

这段代码的意思是,找出自然整数中小于 10000 的同时是乘方数和奇数的数字,再把这些数加总。由于 Haskell 的懒运算特性,上面的程序并不会立马生成从 1 到 无限大的自然数列表,而是会等待 takeWhile 指令,再生成符合条件的列表。如果用 JS 来写,很难写出这么简洁高表达性的代码。一个可能的思路就是写个 while 循环,然后找到符合条件的数进行加总。这个比较简单,我就不演示了。

但是如果我们要用高阶函数来模拟 Haskell 的写法,就要想个办法实现懒运算了。提到懒,首先想到的就是 Iterator 。没人踢它一脚告诉它 next(),它会一直坐那儿不动的。

现在我们就来用 Iterator 来实现一个懒运算。

首先定义一个生成从 1 到无穷大自然数的 generator :

const numbers = function*() {
 let i = 1
 while (true) {
 yield i++
 }
}

由于只有在 generator 执行后生成的 iterable 上执行 next() 方法,yield 才会执行,所以我们要做的主要工作就是实现不同的 next 方法,达到目的。

我们需要先创建一个工厂函数 Lazy,Lazy 封装了我们的各种目标操作 :

const Lazy = iterator => {
 const next = iterable.next.bind(iterable)
 const map = () => {}
 const filter = () => {}
 const takeWhile = () => {}
 return {
  next,
  map,
  filter,
  takeWhile,
 }

我们先实现 map 方法,它会把每次 next 返回的值根据提供的回调函数进行修改:

const map = f => {
 const modifiedNext = () => {
 const item = next()
 const mappedValue = f(item.value)
 return {
  value: mappedValue,
  done: item.done,
 }
 }
 const newIter = { ...iterable, next: modifiedNext }
 return lazy(newIter)
}

再定义 filter 方法,它会让 next 只返回符合判断条件的值:

const filter = predicate => {
 const modifiedNext = () => {
 while (true) {
  const item = next()
  if (predicate(item.value)) {
  return item
  }
 }
 }
 const newIter = { ...iterable, next: modifiedNext }
 return lazy(newIter)
}

最后,定义 takeWhile,它会限制 next 执行的条件,一旦条件不满足,则停止执行 next 并返回历史执行结果:

const takeWhile = predicate => {
 const result = []
 let value = next().value
 while (predicate(value)) {
 result.push(value)
 value = next().value
 }
 return result
}

主要的方法都定义完了,现在把它们合并起来:

const Lazy = iterable => {
 const next = iterable.next.bind(iterable)

 const map = f => {
 const modifiedNext = () => {
  const item = next()
  const mappedValue = f(item.value)
  return {
  value: mappedValue,
  done: item.done,
  }
 }
 const newIter = { ...iterable, next: modifiedNext }
 return lazy(newIter)
 }

 const filter = predicate => {
 const modifiedNext = () => {
  while (true) {
  const item = next()
  if (predicate(item.value)) {
   return item
  }
  }
 }
 const newIter = { ...iterable, next: modifiedNext }
 return lazy(newIter)
 }

 const takeWhile = predicate => {
 const result = []
 let value = next().value
 while (predicate(value)) {
  result.push(value)
  value = next().value
 }
 return result
 }

 return Object.freeze({
 map,
 filter,
 takeWhile,
 next,
 })
}

const numbers = function*() {
 let i = 1
 while (true) {
 yield i++
 }
}

现在用我们写的 Lazy 和 numbers 函数来实现文章开头的 Haskell 代码:

Lazy(numbers())
 .map(x => x ** 2)
 .filter(x => x % 2 === 1)
 .takeWhile(x => x < 10000)
 .reduce((x, y) => x + y)
// => 16650

参考:

Lazy Evaluation in JavaScript with Generators, Map, Filter, and Reduce

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
深入理解JavaScript系列(19):求值策略(Evaluation strategy)详解
Mar 05 Javascript
jquery读取xml文件实现省市县三级联动的方法
May 29 Javascript
JavaScript程序设计之JS调试
Dec 09 Javascript
Node.js项目中调用JavaScript的EJS模板库的方法
Mar 11 Javascript
JavaScript隐式类型转换
Mar 15 Javascript
全面了解JS中的匿名函数
Jun 29 Javascript
JS回调函数基本定义与用法实例分析
May 24 Javascript
JS动态修改网页body的背景色实例代码
Oct 07 Javascript
JQuery选中select组件被选中的值方法
Mar 08 jQuery
Javascript实现时间倒计时功能
Nov 17 Javascript
JavaScript原始值与包装对象的详细介绍
May 11 Javascript
vue Element-ui表格实现树形结构表格
Jun 07 Vue.js
JavaScript创建对象的四种常用模式实例分析
Jan 11 #Javascript
详解Vue项目部署遇到的问题及解决方案
Jan 11 #Javascript
VeeValidate 的使用场景以及配置详解
Jan 11 #Javascript
JS函数节流和防抖之间的区分和实现详解
Jan 11 #Javascript
微信公众号H5支付接口调用方法
Jan 10 #Javascript
详解在Node.js中发起HTTP请求的5种方法
Jan 10 #Javascript
vue实现压缩图片预览并上传功能(promise封装)
Jan 10 #Javascript
You might like
php自动适应范围的分页代码
2008/08/05 PHP
PHP向浏览器输出内容的4个函数总结
2014/11/17 PHP
php打包网站并在线压缩为zip
2016/02/13 PHP
php利用imagemagick实现复古老照片效果实例
2017/02/16 PHP
Moment.js 不容错过的超棒Javascript日期处理类库
2012/04/15 Javascript
javascript变量作用域使用中常见错误总结
2013/03/26 Javascript
jtable列中自定义button示例代码
2013/11/21 Javascript
Mac下使用charles遇到的问题以及解决办法
2017/01/10 Javascript
JS实现的全排列组合算法示例
2017/10/09 Javascript
Javascript 实现 Excel 导入生成图表功能
2018/10/22 Javascript
javascript中call()、apply()的区别
2019/03/21 Javascript
微信小程序事件对象中e.target和e.currentTarget的区别详解
2019/05/08 Javascript
解决layui数据表格排序图标被超出的表头挤出去的问题
2019/09/19 Javascript
Vue组件简易模拟实现购物车
2020/12/21 Vue.js
Python批量修改文件后缀的方法
2014/01/26 Python
python的迭代器与生成器实例详解
2014/07/16 Python
利用Anaconda完美解决Python 2与python 3的共存问题
2017/05/25 Python
PyQt5每天必学之日历控件QCalendarWidget
2018/04/19 Python
python实现超市扫码仪计费
2018/05/30 Python
Django objects的查询结果转化为json的三种方式的方法
2018/11/07 Python
对Python信号处理模块signal详解
2019/01/09 Python
使用Python通过oBIX协议访问Niagara数据的示例
2020/12/04 Python
利用python爬取有道词典的方法
2020/12/08 Python
英国在线药房和在线医生:LloydsPharmacy
2019/10/21 全球购物
瑞典最大的儿童用品网上商店:pinkorblue.se
2021/03/09 全球购物
我有一个char * 型指针正巧指向一些int 型变量, 我想跳过它们。 为什么如下的代码((int *)p)++; 不行?
2013/05/09 面试题
物流司机岗位职责
2013/12/28 职场文书
幼儿园新学期寄语
2014/01/18 职场文书
心理健康教育制度
2014/01/27 职场文书
安全教育实施方案
2014/03/02 职场文书
合作协议书范本
2014/04/17 职场文书
英文升职感谢信
2015/01/23 职场文书
旷课检讨书范文
2015/01/27 职场文书
会计手工模拟做账心得体会
2016/01/22 职场文书
初中语文教学反思范文
2016/03/03 职场文书
详解MySQL中timestamp和datetime时区问题导致做DTS遇到的坑
2021/12/06 MySQL