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 相关文章推荐
清空上传控件input file的值
Jul 03 Javascript
jQuery 遍历- 关于closest() 的方法介绍以及与parents()的方法区别分析
Apr 26 Javascript
可以用鼠标拖动的DIV实现思路及代码
Oct 21 Javascript
特殊情况下如何获取span里面的值
May 20 Javascript
js简单正则验证汉字英文及下划线的方法
Nov 28 Javascript
jQuery插件zTree实现单独选中根节点中第一个节点示例
Mar 08 Javascript
AngularJS页面传参的5种方式
Apr 01 Javascript
Vue.js仿Metronic高级表格(二)数据渲染
Apr 19 Javascript
JS表单提交验证、input(type=number) 去三角 刷新验证码
Jun 21 Javascript
Vue2.0基于vue-cli+webpack同级组件之间的通信教程(推荐)
Sep 14 Javascript
vue中如何动态绑定图片,vue中通过data返回图片路径的方法
Feb 07 Javascript
JS监听滚动和id自动定位滚动
Dec 18 Javascript
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 应用程序安全防范技术研究
2009/09/25 PHP
关于PHP结束标签的使用细节探讨及联想
2013/03/04 PHP
基于php-fpm 参数的深入理解
2013/06/03 PHP
一个很酷的拖动层的js类,兼容IE及Firefox
2009/06/23 Javascript
Javascript匿名函数的一种应用 代码封装
2010/06/27 Javascript
JavaScript数组常用方法
2015/03/02 Javascript
js计算系统当前日期是星期几的方法
2016/07/14 Javascript
微信小程序 Flex布局详解
2016/10/09 Javascript
JavaScript+Html5实现按钮复制文字到剪切板功能(手机网页兼容)
2017/03/30 Javascript
springMVC + easyui + $.ajaxFileUpload实现文件上传注意事项
2017/04/23 Javascript
H5上传本地图片并预览功能
2017/05/08 Javascript
Angular.js实现动态加载组件详解
2017/05/28 Javascript
js获取元素的偏移量offset简单方法(必看)
2017/07/05 Javascript
IScroll那些事_当内容不足时下拉刷新的解决方法
2017/07/18 Javascript
SVG动画vivus.js库使用小结(实例代码)
2017/09/14 Javascript
深入理解Vue生命周期、手动挂载及挂载子组件
2017/09/27 Javascript
Node.js 使用request模块下载文件的实例
2018/09/05 Javascript
vue 刷新之后 嵌套路由不变 重新渲染页面的方法
2018/09/13 Javascript
微信开发之微信jssdk录音功能开发示例
2018/10/22 Javascript
GOJS+VUE实现流程图效果
2018/12/01 Javascript
详解使用React.memo()来优化函数组件的性能
2019/03/19 Javascript
Vue CLI2升级至Vue CLI3的方法步骤
2019/05/20 Javascript
Python脚本实现DNSPod DNS动态解析域名
2015/02/14 Python
python 连接sqlite及简单操作
2017/06/30 Python
python3 json数据格式的转换(dumps/loads的使用、dict to str/str to dict、json字符串/字典的相互转换)
2019/04/01 Python
django使用xadmin的全局配置详解
2019/11/15 Python
python开发入门——set的使用
2020/09/03 Python
意大利制造的西装、衬衫和针对男士量身定制的服装:Lanieri
2018/04/08 全球购物
天游软件面试
2013/11/23 面试题
迟到检讨书300字
2014/02/14 职场文书
新教师个人总结
2015/02/06 职场文书
因家庭原因离职的辞职信范文
2015/05/12 职场文书
Spring boot应用启动后首次访问很慢的解决方案
2021/06/23 Java/Android
分享几个简单MySQL优化小妙招
2022/03/31 MySQL
Python+DeOldify实现老照片上色功能
2022/06/21 Python
win10蓝屏0xc0000001安全模式进不了怎么办?win10出现0xc0000001的解决方法
2022/08/05 数码科技