Бесконечный поток:
val ones: Stream[Int] = Stream.cons(1, ones)
Как можно использовать значение в своей собственной декларации? Кажется, это должно привести к ошибке компилятора, но все же работает.
Бесконечный поток:
val ones: Stream[Int] = Stream.cons(1, ones)
Как можно использовать значение в своей собственной декларации? Кажется, это должно привести к ошибке компилятора, но все же работает.
Это не всегда рекурсивное определение. Это действительно работает и производит 1:
val a : Int = a + 1
println(a)
variable a
создается при вводе val a: Int
, поэтому вы можете использовать его в определении. Int
по умолчанию инициализируется 0. Класс будет нулевым.
Как заметил @Chris, Stream принимает => Stream[A]
, так что некоторые другие правила применяются, но я хотел объяснить общий случай. Идея остается прежней, но переменная передается по-имени, поэтому это приводит к рекурсивному вычислению. Учитывая, что он передается по имени, он выполняется лениво. Поток вычисляет каждый элемент один за другим, поэтому он вызывает ones
каждый раз, когда ему нужен следующий элемент, в результате чего тот же самый элемент создается еще раз. Это работает:
val ones: Stream[Int] = Stream.cons(1, ones)
println((ones take 10).toList) // List(1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
Хотя вы можете сделать бесконечный поток проще: Stream.continually(1)
Обновить Как @SethTisue указал в комментариях Stream.continually
и Stream.cons
два совершенно разных подхода с очень разными результатами, потому что cons
принимает a
, когда continually
принимает =>A
, что означает, что continually
перекомпонирует каждый раз элемент и сохраняет его в памяти, когда cons
может избежать его n раз, если вы не преобразуете его в другую структуру, например List
. Вы должны использовать continually
, только если вам нужно генерировать разные значения. См. Комментарий @SethTisue для деталей и примеров.
Но обратите внимание, что вам необходимо указать тип, то же, что и с рекурсивными функциями.
И вы можете сделать первый пример рекурсивным:
lazy val b: Int = b + 1
println(b)
Это будет stackoverflow.
Посмотрите на подпись Stream.cons.apply:
apply[A](hd: A, tl: ⇒ Stream[A]): Cons[A]
⇒
по второму параметру указывает, что он имеет семантику по имени. Поэтому ваше выражение Stream.cons(1, ones)
строго не оценивается; аргумент ones
не нужно вычислять до передачи в качестве аргумента для tl
.
Причина, по которой это не приводит к ошибке компилятора, заключается в том, что как Stream.cons
, так и Cons
нестрогий и лениво оценивают свой второй параметр.
ones
может использоваться в его собственном определении, поскольку объект cons имеет метод apply, определенный следующим образом:
/** A stream consisting of a given first element and remaining elements
* @param hd The first element of the result stream
* @param tl The remaining elements of the result stream
*/
def apply[A](hd: A, tl: => Stream[A]) = new Cons(hd, tl)
И минус определяется следующим образом:
final class Cons[+A](hd: A, tl: => Stream[A]) extends Stream[A]
Обратите внимание, что второй параметр tl
передается по имени (=> Stream[A]
), а не по значению. Другими словами, параметр tl
не оценивается до тех пор, пока он не будет использован в этой функции.
Одно из преимуществ использования этого метода состоит в том, что вы можете составлять сложные выражения, которые могут быть частично оценены.