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

作者:木棉

时间:2026-10-22

浏览:126

本文介绍如何在JavaScript中通过Iterator和Generator实现惰性求值,模拟Haskell的高阶函数行为。通过封装map、filter和takeWhile方法,构建Lazy对象,实现按需计算,避免生成无限列表,提升代码表达性。

文章目录

引言:从 Haskell 到 JavaScript 的惰性求值

前言

在探究 Haskell 语言特性时,观察到一种特定的编程范式:


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

该代码旨在计算小于 10000 且同时为奇数与完全平方数的自然数之和。得益于 Haskell 的惰性求值机制,程序不会立即构建从 1 到无穷的完整列表,而是依据 takeWhile 的约束动态生成数据。若使用 JavaScript 实现同等逻辑,代码往往缺乏 Haskell 的简洁性与高表达力。一种直观的替代方案是编写显式的 while 循环进行累加,此处略去演示。

然而,若试图通过高阶函数模拟 Haskell 的行为,必须解决惰性求值的问题。实现惰性的核心机制在于 Iterator。只要不主动调用 next() 方法,迭代器将保持静止状态。

接下来,我们将利用 Iterator 构建惰性求值系统。

首先,定义一个生成从 1 开始无穷自然数的 generator:

实现惰性求值的核心:Iterator 与 Generator


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

关键点在于,只有当对 generator 生成的 iterable 调用 next() 时,yield 语句才会执行。因此,核心任务在于定制不同的 next 方法以实现预期功能。

我们需要构建一个工厂函数 Lazy,用于封装各类目标操作:

构建 Lazy 工厂与高阶操作:map、filter 和 takeWhile


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 代码:

完整示例:模拟 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

总结

本文阐述了上述全部内容,期望对读者的学习或工作有所助益。若有任何疑问,欢迎留言交流,感谢支持本站。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章