Javascript функциональный ленивый пример оценки
Просмотр 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
, возвращенный первым вызовом naturalNumbers
, будет равен 1, а следующий элемент в потоке будет оцениваться как ones().add( naturalNumbers() );
, который равен ones().add(1)
, за которым следует ones().add( naturalNumbers() )
, который будет reeavulate до 1
и т.д....
Было бы очень приятно, если бы кто-то пролил свет на это.
Ответы
Ответ 1
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()
не вызывает непосредственно. Скорее, он вызывает косвенную связь - опосредуется суммирующим потоком.
Ответ 2
Хорошо, я возьму это на:)
ones
- простая отправная точка. Эта функция возвращает Stream
, первое значение которого 1
, а оставшиеся значения могут быть вычислены путем вызова самой функции ones
. Поэтому любой запрос для "остатка" значений one
всегда начинается с 1
, ad infinitum.
Следующее, что нужно посмотреть, это функция 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
в потоке ones
с результатом вызова функции naturalNumbers
. Таким образом, мы можем догадаться, что это в некоторой степени связано с "спариванием" двух Stream
.
Как выглядит добавление? Это функция, которая передается a Stream
:
function (s) {
return this.zip(function (x, y) {
return x + y;
}, s);
}
Мы можем распознать часть "добавить" как внутреннюю функцию - она добавляет два значения вместе, поэтому это имеет смысл. Но что делает zip
? zip
- это функция, которая принимает функцию и a 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());
});
}
Итак, в случае добавления функция, переданная в, была функцией "добавить" (x + y), а Stream
была naturalNumbers
Stream
.
Что делает zip
с этими значениями? Если сам Stream
пуст, возвращается "other" Stream
. Думаю, это связано с тем, что добавление [] к [2,4,6,8,...] имеет смысл [2,3,6,8,...], чем что-либо еще. то есть первый поток обрабатывается как бесконечное число 0
или ""
.
Если переданный Stream
пуст, то применяется то же правило, что и выше, только наоборот. то есть добавление [2,4,6,8,...] к [].
Теперь самое интересное. После записи ссылки на себя возвращается новый Stream
. Этот Stream
состоит из значения главы, которое является функцией "добавить", применяемой к элементам заголовка каждого Stream
, и функции, которая будет применять функцию "добавить" к хвосту каждого Stream
, если это необходимо.
Таким образом, в случае ones().add(naturalNumbers())
это приведет к Stream
, голова которого 2
, так как функция "add" вызывается с помощью 1
и 1
(элемент head как ones
и naturalNumbers
являются 1
). Поэтому, если этот новый Stream
предлагается добавить в ones
, то он будет ones
элемент head (всегда 1
) добавлен к новому элементу головы Stream
(теперь 2
), давая 3
.
Хвост этого нового Stream
является механизмом для доставки дополнительных "добавлений", если требуется.
Таким образом, мы остаемся в основном способом описания операций, применяемых к элементам головы и хвосту. Только когда мы приходим, чтобы спросить определенное количество предметов, мы проходим через механизм для создания этих элементов.
Итак, если вы вызвали ones().take(9999999999999999999999999999999).print()
, тогда потребуется много ресурсов, так как функция print
должна иметь значение, прежде чем оно сможет его распечатать, - это обязательно заставляет этот механизм доставлять столько 1
s, Но ones().take(9999999999999999999999999999999)
сам по себе является просто описанием элемента head 1
и процесса доставки остальных элементов, но только если его просят.
Но... Я мог бы получить это совершенно неправильно, потому что это слишком поздно для меня, и я только что прочитал статью;)
Ответ 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.
Пример "сита" в botton http://streamjs.org еще более закручивается, попробуйте!