Как ленивая функция take вычисляет поток Scala дальше? - PullRequest
3 голосов
/ 27 марта 2020

В книге Мартина Одерского «Программирование в Scala» приведен пример вычисления последовательности Фибоначчи, начиная с 2-х чисел, переданных в качестве аргументов функции fibFrom.

def fibFrom(a: Int, b: Int): Stream[Int] =
       a #:: fibFrom(b, a + b)

Если вы примените метод take () к этой рекурсивной функции, например:

fibFrom(1, 1).take(15).print

Вывод будет:

1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, empty

Возможно, это вывод очевидно для более опытных людей, но я не понимаю, как именно этот метод take () делает поток для дальнейшего расчета. 15 как-то неочевидно передается в fibFrom ()?

Ответы [ 2 ]

2 голосов
/ 27 марта 2020

Я думаю, что следует отметить, что Stream лениво оценивается :

Класс Stream реализует ленивые списки, в которых элементы оцениваются только тогда, когда они необходимы. ,

Цитируется из scala -lang.org

fibFrom функция возвращает Stream, поэтому мы понимаем, что функция не будет ничего не делать, даже когда его зовут; он начнет вычислять числа только при попытке доступа к функции Stream.
take, также возвращает Stream и действует лениво.
Функция print - это та, которая фактически вызывает рекурсию и останавливается при заполнении вывода 15 числами (как в вашем примере).

Вы можете легко проверить это, выполняя функции одну за другой и просматривая вывод.
Давайте работать только fibFrom:
enter image description here
Мы можем обратите внимание, что возвращаемое значение равно Stream, а числа еще не рассчитаны.

Теперь давайте посмотрим, что делает take(15):
enter image description here
То же, что и наш первый тест.

В конце концов, выполнение print дает нам желаемый результат, таким образом фактически рекурсивно запускает fibFrom до достижения 15 чисел:
enter image description here

Бонус: Преобразование потока в любую не ленивую структуру данных приведет к вычислениям:
enter image description here

1 голос
/ 27 марта 2020

С помощью

a #:: fibFrom(b, a + b)

вы создали объект Stream, и у этого объекта есть голова, которая является Int, и хвост, который является функцией. Take - это функция Stream, которая вычислит 15 элементов, используя хвост и голову. Вы можете проверить исходный код функции take ():

  override def take(n: Int): Stream[A] = (
    // Note that the n == 1 condition appears redundant but is not.
    // It prevents "tail" from being referenced (and its head being evaluated)
    // when obtaining the last element of the result. Such are the challenges
    // of working with a lazy-but-not-really sequence.
    if (n <= 0 || isEmpty) Stream.empty
    else if (n == 1) cons(head, Stream.empty)
    else cons(head, tail take n-1)
  )
...