Переменная, используемая в собственном определении?

Бесконечный поток:

val ones: Stream[Int] = Stream.cons(1, ones)

Как можно использовать значение в своей собственной декларации? Кажется, это должно привести к ошибке компилятора, но все же работает.

Ответ 1

Это не всегда рекурсивное определение. Это действительно работает и производит 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.

Ответ 2

Посмотрите на подпись Stream.cons.apply:

apply[A](hd: A, tl: ⇒ Stream[A]): Cons[A]

по второму параметру указывает, что он имеет семантику по имени. Поэтому ваше выражение Stream.cons(1, ones) строго не оценивается; аргумент ones не нужно вычислять до передачи в качестве аргумента для tl.

Ответ 3

Причина, по которой это не приводит к ошибке компилятора, заключается в том, что как 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 не оценивается до тех пор, пока он не будет использован в этой функции.

Одно из преимуществ использования этого метода состоит в том, что вы можете составлять сложные выражения, которые могут быть частично оценены.