rafl
rafl

Reputation: 12339

Scala instance value scoping

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:

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.

Accessing the value at different times may result in different results. To me, it very much seems like a compiler implementation detail leaking out.

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.

One could, presumably, declare all instance vals 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?

Upvotes: 3

Views: 302

Answers (2)

Daniel C. Sobral
Daniel C. Sobral

Reputation: 297155

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.

A constructor is a constructor. You are constructing the object. All of its fields are initialized by JVM (basically, zeroed), and then the constructor fills in whatever fields needs filling in.

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

I have no idea what you are saying or asking here, but a constructor is not a method.

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

It doesn't. If you try to modify bar from the constructor, you'll see it is not possible. Accessing the value at different times in the constructor may result in different results, of course.

You are constructing the object: it starts not constructed, and ends constructed. For it not to change it would have to start out with its final value, but how can it do that without someone assigning that value?

Guess who does that? The constructor.

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

There's no use case for accessing the val before its value has been filled in. It's just impossible to find out whether it has been initialized or not. For example:

class Foo {
  println(bar)
  val bar = 10
}

Do you think the compiler can guarantee it has not been initialized? Well, then open the REPL, put in the above class, and then this:

class Bar extends { override val bar = 42 } with Foo
new Bar

And see that bar was initialized when printed.

  • How would one work around this behaviour of val, getting back all the guarantees one is used to from using it within other methods?

Declare your vals before using them. But note that constuctor is not a method. When you do:

println(bar)

inside a constructor, you are writing:

println(this.bar)

And this, the object of the class you are writing a constructor for, has a bar getter, so it is called.

When you do the same thing on a method where bar is a definition, there's no this with a bar getter.

Upvotes: 2

user unknown
user unknown

Reputation: 36229

Only a partial answer:

Given that a constructor is really just a method ...

It isn't.

  • It doesn't return a result and doesn't declare a return type (or doesn't have a name)
  • It can't be called again for an object of said class like "foo".new ("bar")
  • You can't hide it from an derived class
  • You have to call them with 'new'
  • Their name is fixed by the name of the class

Ctors look a little like methods from the syntax, they take parameters and have a body, but that's about all.

  • Why does val, effectively, lose its effect of declaring an immutable value?

It doesn't. You have to take an elementary type which can't be null to get this illusion - with Objects, it looks different:

object Foo {
  def foo = bar
  println (bar.mkString)
  val bar = List(42)
}
// Exiting paste mode, now interpreting.

defined module Foo

scala> val foo=Foo 
java.lang.NullPointerException

You can't change a val 2 times, you can't give it a different value than null or 0, you can't change it back, and a different value is only possible for the elementary types. So that's far away from being a variable - it's a - maybe uninitialized - final value.

  • What might legitimate usecases for this look like?

I guess working in the REPL with interactive feedback. You execute code without an explicit wrapping object or class. To get this instant feedback, it can't be waited until the (implicit) object gets its closing }. Therefore the class/object isn't read in a two-pass fashion where firstly all declarations and initialisations are performed.

  • How would one work around this behaviour of val, getting back all the guarantees one is used to from using it within other methods?

Don't read attributes in the Ctor, like you don't read attributes in Java, which might get overwritten in subclasses.

update

Similar problems can occur in Java. A direct access to an uninitialized, final attribute is prevented by the compiler, but if you call it via another method:

public class FinalCheck
{
    final int foo;
    
    public FinalCheck ()
    {
        // does not compile: 
        // variable foo might not have been initialized
        // System.out.println (foo);
        
        // Does compile - 
        bar ();

        foo = 42;       
        System.out.println (foo);
    }

    public void bar () {
        System.out.println (foo);   
    }
    public static void main (String args[])
    {
        new FinalCheck ();
    }
}

... you see two values for foo.

0
42

I don't want to excuse this behaviour, and I agree, that it would be nice, if the compiler could warn consequently - in Java and Scala.

Upvotes: 3

Related Questions