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

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

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

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


person axiopisty    schedule 05.11.2015    source источник
comment
Точно так же работают рекурсивные функции. Думайте, что ones — это функция без аргументов.   -  person n. 1.8e9-where's-my-share m.    schedule 05.11.2015


Ответы (3)


Это не всегда рекурсивное определение. Это действительно работает и производит 1:

val a : Int = a + 1
println(a)

переменная a создается при вводе val a: Int, поэтому ее можно использовать в определении. Int по умолчанию инициализируется 0. Класс будет нулевым.

Как указал @Chris, Stream принимает => Stream[A], поэтому применяются немного другие правила, но я хотел объяснить общий случай. Идея остается той же, но переменная передается по имени, что делает вычисления рекурсивными. Учитывая, что он передается по имени, он выполняется лениво. Stream вычисляет каждый элемент один за другим, поэтому он вызывает 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)

Это приведет к переполнению стека.

person Archeg    schedule 05.11.2015
comment
Просто примечание: Stream.cons(1, ones) — это циклическая структура, использующая конечную память, а Stream.continually(1) — линейная структура, использующая потенциально неограниченную память. - person Seth Tisue; 05.11.2015
comment
@SethTisue Не могли бы вы объяснить? Есть ли практическая разница? Не уверен, что вы имеете в виду. Если вы сделаете Stream.continually(1).take(10000000), все равно не будут выделены все эти номера - person Archeg; 05.11.2015
comment
Существует огромная практическая разница между чем-то, что использует только несколько слов памяти, и чем-то, что может потреблять всю кучу. (Обратите внимание, однако, что как только вы вызываете take, вы получаете одну и ту же структуру в любом случае, поэтому важно ли это, зависит от того, что вы на самом деле делаете с потоком.) Дополнительная литература: gist.github.com/SethTisue/ce598578874accba98c0, groups.google.com/d/msg/scala-user/3yypUKJBP04/Q_bowgIry44J - person Seth Tisue; 05.11.2015
comment
@SethTisue вау! Я не понимал, что cons берет A, когда continually берет =>A. Я согласен, что это делает эти два метода совершенно разными. - person Archeg; 05.11.2015


Причина, по которой это не приводит к ошибке компилятора, заключается в том, что и Stream.cons, и Cons являются нестрогими и лениво оценивает второй параметр.

ones можно использовать в своем собственном определении, потому что у объекта cons есть метод применения, определенный следующим образом:

/** 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)

А Cons определяется так:

final class Cons[+A](hd: A, tl: => Stream[A]) extends Stream[A]

Обратите внимание, что второй параметр tl передается по имени (=> Stream[A]), а не по значению. Другими словами, параметр tl не оценивается, пока он не используется в функции.

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

person axiopisty    schedule 05.11.2015