Scala error: “forward reference extends over defin

2019-04-17 18:17发布

问题:

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.)

回答1:

There was an answer here, but it was deleted for some reason.

There are basically two options. You could make your val into lazy val. Or you could define your lucas: Stream[Int] in a class as a field. You can parameterize the class with p and q 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 Java int a = 1 + a won't work. Java will try to use a in 1 + a, but a is not yet initialized. Even if Java had Integer a = 1 + a, and a would be a reference, Java still not able to execute this, because Java runs 1 + a statement when allocating a

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 as lazy 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 in lazy val case.

Also please note that because your stream leaves the scope and also because you have two parameters for your stream - p and q, your stream will be recomputed each call if you go with lazy val option. If you choose creating an additional class - you are able to control this, by caching all instances of this class for each p and q possible

P.S. By saying Java here I of course mean JVM. It just easier to think in terms of Java