Note that this question and similar ones have been asked before, such as in Forward References - why does this code compile?, but I found the answers to still leave some questions open, so I'm having another go at this issue.
Within methods and functions, the effect of the val
keyword appears to be lexical, i.e.
def foo {
println(bar)
val bar = 42
}
yielding
error: forward reference extends over definition of value bar
However, within classes, the scoping rules of val
seem to change:
object Foo {
def foo = bar
println(bar)
val bar = 42
}
Not only does this compile, but also the println
in the constructor will yield 0
as its output, while calling foo
after the instance is fully constructed will result in the expected value 42
.
So it appears to be possible for methods to forward-reference instance values, which will, eventually, be initialised before the method can be called (unless, of course, you're calling it from the constructor), and for statements within the constructor to forward-reference values in the same way, accessing them before they've been initialised, resulting in a silly arbitrary value.
From this, a couple of questions arise:
- Why does
val
use its lexical compile-time effect within constructors?
Given that a constructor is really just a method, this seems rather inconsistent to entirely drop val
's compile-time effect, giving it its usual run-time effect only.
- Why does
val
, effectively, lose its effect of declaring an immutable value?
Accessing the value at different times may result in different results. To me, it very much seems like a compiler implementation detail leaking out.
- What might legitimate usecases for this look like?
I'm having a hard time coming up with an example that absolutely requires the current semantics of val
within constructors and wouldn't easily be implementable with a proper, lexical val
, possibly in combination with lazy
.
- How would one work around this behaviour of
val
, getting back all the guarantees one is used to from using it within other methods?
One could, presumably, declare all instance val
s to be lazy
in order to get back to a val
being immutable and yielding the same result no matter how they are accessed and to make the compile-time effect as observed within regular methods less relevant, but that seems like quite an awful hack to me for this sort of thing.
Given that this behaviour unlikely to ever change within the actual language, would a compiler plugin be the right place to fix this issue, or is it possible to implement a val
-alike keyword with, for someone who just spent an hour debugging an issue caused by this oddity, more sensible semantics within the language?