2016-01-31 14 views
0

Я пытаюсь скомпилировать следующий код, используя Scala 2.11.7.Ошибка Scala: «forward reference extends over definition of value», когда код появляется в функции

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 основан на Fibonacci sequence example in Scala's Stream documentation, и она работает.

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

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, поэтому, если объяснение включает термины типа «синтетический» и «неявный», то мне, вероятно, также понадобится дополнительное объяснение этого.)

+0

«Компиляция с -Xprint: диагностика typer - не уверен, что делать с этой информацией». Пожалуйста, вставьте это здесь. – Jus12

+0

@ Jus12 Есть много смысла в вставке вывода? Любой, у кого есть компилятор Scala, может воспроизвести его, компилируя приведенный выше код. –

ответ

4

ответ здесь, но по какой-то причине он был удален.

Существует в основном два варианта. Вы можете сделать свой 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