Appearance
25.3 对比立即求值
实际上,从代码组织逻辑上来说,迭代器模式已经是相对高阶一点的写法。对于一个刚刚接触编程的初学者来说,用下面这种写法才是最常见的:
rust
// 方案三
fn collector() -> Vec<u64> {
let mut res = vec![];
let mut curr : u64 = 1;
let mut next : u64 = 1;
loop {
let new_next = curr.checked_add(next);
if let Some(new_next) = new_next {
curr = next;
next = new_next;
res.push(curr);
} else {
break;
}
}
return res;
}
fn main() {
let collected = collector();
let mut it = collected.iter();
while let Some(i) = it.next() {
println!("{}", i);
}
}
在这个方案中,我们用一个循环把 Fibonacci 数列提前生成出来了,存储在一个动态数组里,然后再去使用。这种做法可以看作是惰性求值的反向操作,叫作“立即求值”(eager evaluation)。不过,它有性能上的缺点,方案三提前把数据收集起来,缺少了灵活性。如果使用者只需要使用这个序列的前 10 个数据呢?如果是方案二迭代器的那种写法,使用者可以选择遍历 10 个元素后就提前 break;后面的数据既不需要生产,也不需要消费,还节省了一个临时的占用很大内存空间的容器,这就是“惰性求值”的好处。如果我们把方案三改成方案二迭代器的写法,性能和灵活性更佳,但是需要人工推理:哪些数据是需要存储在迭代器成员中的,哪些是不需要的,进入 next 方法时如何读取上一次的状态,退出 next 方法时如何保存这一次的状态等。这些都是“心智负担”。业务逻辑越复杂,这个负担越严重。