数组高阶函数会在每一步都热切地完成整个事情。
const isOdd = v => v % 2 == 1;
const multiply = by => v => v * by;
const arrRange = IntStream.range(10, 20);
const arrOdd = arrRange.filter(isOdd);
const arrOddM3 = arrOdd.map(multiply(3));
这里所有的绑定都是由每个步骤创建的不同数组。即使将它们链接起来,中间数组也总是会生成,并且每个步骤的整个数组都需要在下一步开始之前完成。
const arrOddM3 = IntStream.range(10, 20).filter(isOdd).map(multiply(3));
arrOddM3; // ==> [33, 39, 45, 51, 57]
流是不同的,因为它们只在访问时计算值。流版本看起来非常相似。
const streamOddM3 = Stream.range(10, Infinity).filter(isOdd).map(multiply(3));
streamOddM3; // ==> Stream
请注意,我已将结尾更改为无穷大。我可以这样做,因为它最多计算第一个值,而某些实现在您要求值之前根本不进行任何计算。要强制计算,您可以获取一些值并将它们作为数组返回:
streamOddM3.take(3); // ==> [33, 39, 45]
这是一个基于 SICP videos 的 Stream 实现,其工作方式类似于 Java 的流。
class EmptyStream {
map() {
return this;
}
filter() {
return this;
}
take() {
return [];
}
}
class Stream extends EmptyStream {
constructor(value, next) {
super();
this._next = next;
this.value = value;
}
/**
* This prevents the value to be computed more than once
* @returns {EmptyStream|Stream}
*/
next() {
if( ! (this._next instanceof EmptyStream) ) {
this._next = this._next();
}
return this._next;
}
map(fn) {
return new Stream(fn(this.value), () => this.next().map(fn));
}
filter(fn) {
return fn(this.value) ?
new Stream(this.value, () => this.next().filter(fn)) :
this.next().filter(fn);
}
take(n) {
return n == 0 ? [] : [this.value, ...this.next().take(n && n - 1)];
}
static range(from, to, step = 1) {
if (to !== undefined && ( step > 0 && from > to || step < 0 && from < to )) {
return Stream.emptyStream;
}
return new Stream(from, () => Stream.range(from + step, to, step));
}
}
Stream.emptyStream = new EmptyStream();
Stream 有一些替代品可以代替它们。
在 JavaScript 中,你有 generators(又名协程),你可以创建一个 map 和 filter 生成器函数,它接受一个生成器源并通过该转换成为一个新的生成器。由于它已经在该语言中,因此它可能比 Streams 更匹配,但我对它的研究还不够多,无法制作上述的生成器示例。
在 Clojure 中,您有 transducers,它允许您组合步骤,以便最终列表仅针对使其成为最终结果的元素发生。它们很容易用 JavaScript 实现。