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 еще более закручивается, попробуйте!