浏览Hacker News,我遇到了http://streamjs.org/,这是Javascript中懒惰评估集合的实现。
其中一个例子就是:
function ones() {
return new Stream( 1, ones );
}
function naturalNumbers() {
return new Stream(
// the natural numbers are the stream whose first element is 1...
1,
function () {
// and the rest are the natural numbers all incremented by one
// which is obtained by adding the stream of natural numbers...
// 1, 2, 3, 4, 5, ...
// to the infinite stream of ones...
// 1, 1, 1, 1, 1, ...
// yielding...
// 2, 3, 4, 5, 6, ...
// which indeed are the REST of the natural numbers after one
return ones().add( naturalNumbers() );
}
);
}
naturalNumbers().take( 5 ).print(); // prints 1, 2, 3, 4, 5
也许现在已经太晚了,我错过了这一点,但我不明白这是怎么打印1,2,3,4,5。我希望它能打印1,2,2,2,2并且无限深度递归。我理解ones
将如何打印无限1.我不知道naturalNumbers
如何工作。
通过我的(明显不正确的)逻辑,第一次调用head
返回的Stream
的{{1}}将为1,并且流中的下一个元素将被计算为{ {1}} naturalNumbers
后面跟ones().add( naturalNumbers() );
,它将重新转换为ones().add(1)
,依此类推......
如果有人对此有所了解,我会非常感激。
答案 0 :(得分:11)
naturalNumbers[0] = 1 // by definition
naturalNumbers[1] = ones[0] + naturalNumbers[0] = 1 + 1 = 2
naturalNumbers[2] = ones[1] + naturalNumbers[1] = 1 + 2 = 3
naturalNumbers[3] = ones[2] + naturalNumbers[2] = 1 + 3 = 4
...
关键是function() { return ones().add(naturalNumbers()) }
不返回元素,它返回流。后续元素由该流生成,在这种情况下是“求和”流。因此,与ones()
不同,naturalNumbers()
不会直接调用自身。相反,它间接地调用自己 - 由求和流调解。
答案 1 :(得分:4)
好的,我会接受这个:)
ones
是一个简单的起点。此函数返回Stream
,其第一个值为1
,其余值可以通过调用ones
函数本身来计算。因此,对one
值的“其余”值的任何请求都将始终以1
开头,无限制。
接下来要看的是take
函数:
function (howmany) {
if (this.empty()) {
return this;
}
if (howmany == 0) {
return new Stream;
}
var self = this;
return new Stream(this.head(), function () {
return self.tail().take(howmany - 1);
});
}
所以从上到下,如果Stream
为空,那么请求了多少项无关紧要,因为无法满足该请求,所以Stream
返回它(空的。自我。
如果我们没有请求任何项目,即howmany == 0
,则会返回一个空的Stream
,如果被要求,它本身不会产生任何项目。
最后是有趣的部分。对当前Stream
的引用被锁定到函数范围中,并返回一个新的Stream
。这个新的Stream
创建时与当前Stream
的头部相同,其尾部由一个函数创建,该函数将take
与原始Stream
相比减少一个项目尾巴比来电者要求。因此,当头部是一个项目并且尾部可以生成howmany-1
个项目时,呼叫者将收到一个新的Stream
,可能会提供所请求数量的项目。
naturalNumbers
有点棘手。
naturalNumbers
函数返回Stream
作为其头部的1
,以及一个生成其尾部的内部函数。
内部函数返回在add
Stream上调用ones
方法的结果,调用naturalNumbers
函数的结果。所以我们可以猜测这涉及以某种方式将两个Stream
“配对”。
添加什么样的?这是一个传递Stream
:
function (s) {
return this.zip(function (x, y) {
return x + y;
}, s);
}
我们可以将'add'部分识别为内部函数 - 它将两个值一起添加,这样才有意义。但是zip
做了什么? zip
是一个函数,它接受一个函数并将Stream
作为参数。
function (f, s) {
if (this.empty()) {
return s;
}
if (s.empty()) {
return this;
}
var self = this;
return new Stream(f(s.head(), this.head()), function () {
return self.tail().zip(f, s.tail());
});
}
所以在add的情况下,传入的函数是'add'(x + y)函数,Stream
是naturalNumbers
Stream
。
zip
对这些值的作用是什么?如果Stream
本身为空,则返回“其他”Stream
。我想这是因为将[]添加到[2,4,6,8,...]比[2,3,6,8,...]更有意义。即第一个流被视为无限多0
或""
。
如果传入的Stream
为空,则适用与上述相同的规则,反之亦然。即将[2,4,6,8,...]添加到[]。
现在有趣的部分。捕获对自身的引用后,将返回新的Stream
。这个Stream
由一个head值组成,它是应用于每个Stream
的head元素的'add'函数,以及一个将'add'函数应用于每个Stream
尾部的函数。 1}}如果需要的话。
因此,对于ones().add(naturalNumbers())
,这将导致Stream
的头部为2
,因为使用1
和{{调用'add'函数1}}(1
和ones
的头元素都是naturalNumbers
)。因此,如果要求将此新1
添加到Stream
,那么将ones
头元素(始终为ones
)添加到新1
' s head元素(现为Stream
),给出2
。
这个新3
的尾部是一种在需要时提供更多“添加”的机制。
所以我们剩下的基本上是一种描述应用于头元素和尾部的操作的方法。只有当我们要求特定数量的物品时,我们才能通过机器生成这些物品。
因此,如果您调用了Stream
,则需要大量资源,因为ones().take(9999999999999999999999999999999).print()
函数需要才能在打印之前获得该值 - 这必然会导致此问题机器必须提供那么多print
s。但1
本身只是对头元素ones().take(9999999999999999999999999999999)
的描述以及传递其余项目的过程,但仅在被要求时才会这样。
但是...... 我可能已经完全错了,因为我也迟到了,我只是读了这篇文章;)
答案 2 :(得分:3)
一次只执行一个学期的评估:
ones
= { 1, ones }
= { 1, { 1, ones } }
= ...
= { 1, { 1, { 1, ... to infinity!
nat
= { 1, ones+nat }
= { 1, { 1, ones } + { 1, ones+nat } } = { 1, { 1+1, ones+ones+nat } }
= { 1, { 2, { 1, ones } + { 1, ones } + { 1, nat } } }
= ...
= { 1, { 2, { 3, ... and so on.
http://streamjs.org底部的“筛子”示例更令人头疼,试试吧!