# 惰性处理:短路与流式求值
学习目标:理解惰性序列(Lazy Streams/Iterators)与及早求值(Eager Evaluation)的开销差异,掌握仅消费所需元素时立刻提前短路的实现。
前置知识与范围:循环、闭包与迭代器。演示从大规模(甚至无限)数字流中筛选前 3 条满足条件的记录并即刻短路。
# 问题与边界
从整数序列 1..=10000 中,提取前 3 个能被 3 整除的数值:
- 预期提取结果:
[3, 6, 9]。 - 核心契约(短路验证):
- 一旦收集满 3 个结果,必须立即终止整个数据流的处理。
- 检查的元素总量最多到 9 为止,绝不能执行完剩下的 9991 次无谓判定!
- 必须通过计数器证明谓词调用的短路有效性。
# 关键差异
| 语言 | 惰性流机制 | 限制数量(Take/Limit) | 及早求值陷阱 |
|---|---|---|---|
| Rust | Iterator 适配器(默认全惰性) |
.take(3) |
绝无中间集合;.collect() 触发拉取 |
| Go | 显式 break 循环或闭包生成器 |
手动计数 if len == 3 { break } |
避免使用全量 make 构造万级切片 |
| Java | Stream API |
.filter(...).limit(3) |
Stream 管道属于短路操作(Short-circuiting) |
| JavaScript | 生成器函数 function* 或普通循环 |
消费迭代器调用 3 次后退出 | 直接调用 arr.filter().slice() 会先跑完 10000 次! |
| TypeScript | 生成器函数 IterableIterator |
手动消费或辅助函数 | 规避数组全量及早计算 |
| Kotlin | Sequence<T>(序列) |
asSequence().filter(...).take(3).toList() |
默认 List.filter 是及早求值,必须转 asSequence |
| Python | 生成器表达式 / itertools.islice |
itertools.islice(gen, 3) |
列表推导式会及早计算所有 10000 项 |
| C++ | 显式范围循环带 break 或 C++20 views |
循环累计 3 项立即跳出 | 避免预先分配超大向量 |
# 各语言实现
# Rust
Rustuse std::cell::Cell;
fn main() {
let eval_count = Cell::new(0);
let results: Vec<i32> = (1..=10000)
.filter(|&x| {
eval_count.set(eval_count.get() + 1);
x % 3 == 0
})
.take(3)
.collect();
assert_eq!(results, vec![3, 6, 9]);
// 证明只检查了 9 次,第 9 项满足第 3 个条件后立即短路
assert_eq!(eval_count.get(), 9);
println!("results: {:?}, checks: {}", results, eval_count.get());
}
Rust 迭代器是零成本抽象,take(3) 在满足条件后直接在迭代层切断拉取。
# Go
Gopackage main
import "fmt"
func main() {
evalCount := 0
var results []int
for x := 1; x <= 10000; x++ {
evalCount++
if x%3 == 0 {
results = append(results, x)
if len(results) == 3 {
break // 立即提前短路退出
}
}
}
if len(results) != 3 || results[0] != 3 || results[1] != 6 || results[2] != 9 {
panic("mismatch")
}
if evalCount != 9 {
panic("lazy short-circuit failed")
}
fmt.Printf("results: %v, checks: %d\n", results, evalCount)
}
Go 使用结构清晰的显式计数与 break 达成完全等价的短路控制,执行开销最低。
# Java
Javaimport java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
AtomicInteger evalCount = new AtomicInteger(0);
List<Integer> results = IntStream.rangeClosed(1, 10000)
.filter(x -> {
evalCount.incrementAndGet();
return x % 3 == 0;
})
.limit(3)
.boxed()
.toList();
assert List.of(3, 6, 9).equals(results);
assert evalCount.get() == 9 : "stream should short-circuit at 9 checks";
System.out.println("results: " + results + ", checks: " + evalCount.get());
}
}
Java Stream 的 limit 是原生的短路终结前置操作,元素按需逐个拉取。
# JavaScript
JavaScriptconst assert = {
equal: (a, b) => { if (a !== b) throw new Error(`${a} !== ${b}`); },
deepEqual: (a, b) => { if (JSON.stringify(a) !== JSON.stringify(b)) throw new Error(`${JSON.stringify(a)} !== ${JSON.stringify(b)}`); },
throws: (fn, errType) => {
try { fn(); throw new Error("expected throw"); } catch (e) {
if (e.message === "expected throw") throw e;
if (errType && !(e instanceof errType)) throw e;
}
}
};
function* generateNumbers() {
for (let i = 1; i <= 10000; i++) {
yield i;
}
}
let evalCount = 0;
const results = [];
for (const x of generateNumbers()) {
evalCount++;
if (x % 3 == 0) {
results.push(x);
if (results.length === 3) {
break; // 短路,生成器自动挂起并清理
}
}
}
assert.deepEqual(results, [3, 6, 9]);
assert.equal(evalCount, 9);
console.log(`results: [${results}], checks: ${evalCount}`);
JavaScript 生成器函数 function* 是构造按需惰性数据源的原生工具。
# TypeScript
TypeScriptconst assert = {
equal: (a: unknown, b: unknown) => { if (a !== b) throw new Error(`${a} !== ${b}`); },
deepEqual: (a: unknown, b: unknown) => { if (JSON.stringify(a) !== JSON.stringify(b)) throw new Error(`${JSON.stringify(a)} !== ${JSON.stringify(b)}`); },
throws: (fn: () => void, errType?: unknown) => {
try { fn(); throw new Error("expected throw"); } catch (e: unknown) {
if (e instanceof Error && e.message === "expected throw") throw e;
if (typeof errType === "function" && !(e instanceof errType)) throw e;
}
}
};
function* generateNumbers(): Generator<number, void, unknown> {
for (let i = 1; i <= 10000; i++) {
yield i;
}
}
let evalCount = 0;
const results: number[] = [];
for (const x of generateNumbers()) {
evalCount++;
if (x % 3 == 0) {
results.push(x);
if (results.length === 3) {
break;
}
}
}
assert.deepEqual(results, [3, 6, 9]);
assert.equal(evalCount, 9);
console.log(`results: [${results}], checks: ${evalCount}`);
TypeScript 为 Generator 提供类型标注,消费模式与 JS 保持完全一致。
# Kotlin
Kotlinfun main() {
var evalCount = 0
val results = (1..10000)
.asSequence() // 转换为惰性序列
.filter {
evalCount++
it % 3 == 0
}
.take(3)
.toList()
check(results == listOf(3, 6, 9))
check(evalCount == 9)
println("results: $results, checks: $evalCount")
}
Kotlin 的 Sequence 避免了标准集合操作产生中间列表,实现流水线求值。
# Python
Pythonimport itertools
if __name__ == "__main__":
eval_count = 0
def predicate(x: int) -> bool:
global eval_count
eval_count += 1
return x % 3 == 0
# 生成器表达式:惰性计算
gen = (x for x in range(1, 10001) if predicate(x))
results = list(itertools.islice(gen, 3))
assert results == [3, 6, 9]
assert eval_count == 9
print(f"results: {results}, checks: {eval_count}")
Python 的生成器表达式配合标准库 itertools.islice 实现短路消费,绝不预先计算未请求项。
# C++
C++#include <iostream>
#include <vector>
#include <cassert>
int main() {
int eval_count = 0;
std::vector<int> results;
for (int x = 1; x <= 10000; ++x) {
eval_count++;
if (x % 3 == 0) {
results.push_back(x);
if (results.size() == 3) {
break; // 满足数量立即短路退出
}
}
}
assert(results.size() == 3);
assert(results[0] == 3 && results[1] == 6 && results[2] == 9);
assert(eval_count == 9);
std::cout << "results: [" << results[0] << ", " << results[1] << ", " << results[2]
<< "], checks: " << eval_count << "\n";
}
C++ 中的短路循环不引入复杂的流封装即可达到零内存浪费和极致速度。
# 迁移误区与适用限制
- 及早求值(Eager)导致 OOM:在包含数百万条记录的数组上误写
arr.filter(...).slice(0, 10),会首先将数百万项全量过滤并构建巨大中间数组,直接引发内存溢出。 - 惰性流副作用重复执行:惰性流如果被多次迭代(如多次调用同一个 Java Stream 或已消费的 Python 生成器),会导致不可恢复的流已关闭异常或产生副作用重放。
# 运行与验证
语言版本基线见示例基线。 所有程序运行后控制台输出均为:
纯文本results: [3, 6, 9], checks: 9
短路计数断言验证仅计算了 9 次,剩余 9991 次计算被成功跳过。