I'm trying to compile the following code, using 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
is based on the Fibonacci sequence example in Scala's Stream
documentation, and it works.
However, the firstKind
function, which tries to generalize the sequence with parameters p
and q
(making Lucas sequences of the first kind), has the following error:
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
It's basically the same code, so why does it work outside the function but not inside a function?
This error message has puzzled many programmers before me. I've considered…
- So just don't put that code in a function — but I do want a function.
implicit val lucas
— doesn't help.- Self-references can only be used in lazy expressions — but this is lazy, right?
- Compile with
-Xprint:typer
diagnostics — not sure what to do with that information. - Is it a shadowing issue? — No, I'm using identifiers that don't clash.
- Compiler bug? — I hope not. The referenced bug should be already fixed in 2.11.7.
I could probably go on reading for hours, but I think it would be best to ask for help at this point. I'm looking for both a solution and an explanation. (I'm familiar with functional programming, but new to Scala, so if the explanation involves terms like "synthetic" and "implicit", then I'll probably need an additional explanation of that as well.)
There was an answer here, but it was deleted for some reason.
There are basically two options. You could make your
val
intolazy val
. Or you could define yourlucas: Stream[Int]
in a class as a field. You can parameterize the class withp
andq
in the constructor.You are right that the original code is lazy. But it is not lazy enough for scala to translate it.
For the sake of simplicity think into what code
val a = 1 + a
will be translated (I know the code does not make sense much). In Javaint a = 1 + a
won't work. Java will try to usea
in1 + a
, buta
is not yet initialized. Even if Java hadInteger a = 1 + a
, anda
would be a reference, Java still not able to execute this, because Java runs1 + a
statement when allocatinga
So it leaves us with two options. Defining
a
not as a variable, but as a field. Scala automatically resolve the problem by defining a recursive method, instead of a field - because field in scala is two methods + variable anyway. Or you could tell scala explicitly that it should resolve the lazy problem here by specifying your val aslazy val
. This will make scala generate a hidden class with all the necessary infrastructure for it to be lazy.You can check this behavior by running your compiler with
-print
option. The output is rather complicated though, especially inlazy val
case.Also please note that because your stream leaves the scope and also because you have two parameters for your stream -
p
andq
, your stream will be recomputed each call if you go withlazy val
option. If you choose creating an additional class - you are able to control this, by caching all instances of this class for eachp
andq
possibleP.S. By saying
Java
here I of course mean JVM. It just easier to think in terms of Java