Ошибка Scala: прямая ссылка распространяется на определение значения, когда код появляется в функции

Я пытаюсь скомпилировать следующий код, используя Scala 2.11.7.

object LucasSeq {
  val fibo: Stream[Int] = 0 #:: 1 #:: fibo.zip(fibo.tail).map { pair =>
    pair._1 + pair._2
  }

  def firstKind(p: Int, q: Int): Stream[Int] = {
    val lucas: Stream[Int] = 0 #:: 1 #:: lucas.zip(lucas.tail).map { pair =>
      p * pair._2 - q * pair._1
    }
    lucas
  }
}

fibo основан на примере последовательности Фибоначчи в Stream Scala. документации, и это работает.

Однако функция firstKind, которая пытается обобщить последовательность с параметрами p и q (составляя последовательности Лукаса первого рода), имеет следующую ошибку:

LucasSeq.scala:7: error: forward reference extends over definition of value lucas
    val lucas: Stream[Int] = 0 #:: 1 #:: lucas.zip(lucas.tail).map { pair =>
                                         ^
one error found

По сути, это один и тот же код, так почему же он работает вне функции, но не внутри функции?


Это сообщение об ошибке озадачило многих программистов до меня. Я рассмотрел…

Я, вероятно, мог бы продолжать читать часами, но я думаю, что сейчас лучше обратиться за помощью. Я ищу и решение, и объяснение. (Я знаком с функциональным программированием, но новичок в Scala, поэтому, если объяснение включает такие термины, как «синтетический» и «неявный», то мне, вероятно, потребуется дополнительное объяснение и этого.)


person 200_success    schedule 31.01.2016    source источник
comment
Скомпилируйте с диагностикой -Xprint:typer — не знаю, что делать с этой информацией. Пожалуйста, вставьте это сюда.   -  person Jus12    schedule 31.01.2016
comment
@ Jus12 Есть ли смысл вставлять вывод? Любой, у кого есть компилятор Scala, может воспроизвести его, скомпилировав приведенный выше код.   -  person 200_success    schedule 31.01.2016


Ответы (1)


Здесь был ответ, но его почему-то удалили.

В основном есть два варианта. Вы можете превратить val в lazy val. Или вы можете определить свой lucas: Stream[Int] в классе как поле. Вы можете параметризовать класс с помощью p и q в конструкторе.

Вы правы, исходный код ленив. Но scala не ленится его переводить.

Для простоты подумайте, в какой код будет переводиться val a = 1 + a (я знаю, код не имеет особого смысла). В Java int a = 1 + a не будет работать. Java попытается использовать a в 1 + a, но a еще не инициализирован. Даже если бы в Java было Integer a = 1 + a, а a было бы ссылкой, Java все равно не смогла бы выполнить это, потому что Java запускает оператор 1 + a при распределении a.

Таким образом, это оставляет нам два варианта. Определение a не как переменной, а как поля. Scala автоматически решает проблему, определяя рекурсивный метод вместо поля, потому что поле в scala в любом случае представляет собой два метода + переменную. Или вы можете явно указать scala, что он должен решить проблему лени, указав ваш val как lazy val. Это заставит scala генерировать скрытый класс со всей необходимой инфраструктурой, чтобы он был ленивым.

Вы можете проверить это поведение, запустив компилятор с параметром -print. Однако вывод довольно сложен, особенно в случае lazy val.

Также обратите внимание, что, поскольку ваш поток выходит за рамки, а также потому, что у вас есть два параметра для вашего потока — p и q, ваш поток будет пересчитываться при каждом вызове, если вы выберете опцию lazy val. Если вы решите создать дополнительный класс - вы сможете контролировать это, кэшируя все экземпляры этого класса для каждого p и q возможных

P.S. Говоря Java здесь, я, конечно же, имею в виду JVM. Просто проще думать в терминах Java

person Archeg    schedule 31.01.2016