Puzzler 13

Self: See Self

Recursive variable definitions are a tricky problem in many languages. The simplest such definition is a variable that refers to itself.

The following code attempts to define two such variables. What is the result of executing it in the REPL?

  val s1: String = s1
  val s2: String = s2 + s2
  
println(s1.length) println(s2.length)

Possibilities

  1. Prints:
      0
      0
    
  2. Both println statements throw a NullPointerException.
  3. The first println statement throws a NullPointerException, and the second prints:
      8
    
  4. Both println statements fail to compile.

Explanation

You may wonder whether self-references are allowed at all in Scala. Perhaps the compiler only complains if you apply an operation to the self-reference, as in the second statement.

Alternatively, you might assume that self-referential variables cause the default value to be assigned to the variable, which in this case would result in NullPointerExceptions.

As it happens, both statements indeed compile successfully. However, only one of them throws the anticipated runtime exception:

  scala> println(s1.length)
  java.lang.NullPointerException
    ...
  
scala> println(s2.length) 8

The correct answer, surprisingly enough, is number 3.

Before figuring out what is happening in the second example, it will help to examine how Scala treats recursive definitions of this kind generally. According to The Scala Language Specification, recursive value definitions are indeed valid in Scala.[1] The only condition is that the type of such values must be given explicitly:

  scala> val s = s
  <console>:7: error: recursive value s needs type
         val s = s
                 ^

When the compiler evaluates the expression on the right-hand side of the assignment statement, it uses the default value for any uninitialized variables, as usual:

  scala> val x = y; val y = 10
  <console>:7: warning: Reference to uninitialized value y
         val x = y; val y = 10
                 ^
  x: Int = 0
  y: Int = 10

The default value is also used for any occurrences of the variable itself, which is obviously also still uninitialized. In this case, therefore, s1 is assigned the value null, the default value for Strings and all AnyRefs:

  scala> val s1: String = s1
  <console>:7: warning: value s1 does nothing other than call 
    itself recursively
         val s1: String = s1
                          ^
  s1: String = null

What about s2? Here, similarly, all occurrences of s2 in its declaration are replaced by the default value null, so its initialization statement is null + null.

The Scala compiler converts the concatenation of two string constants into bytecode equivalent to:

  String s2 = (new StringBuilder()).append(null)
              .append(null).toString();

This matches the behavior of Java, as described in the Java Virtual Machine Specification.[2] StringBuilder, in turn, converts the reference null into the string "null".[3] The value of s2 is thus the eight-character string, "nullnull":

  scala> val s2: String = s2 + s2
  s2: String = nullnull

Discussion

Self-referential definitions such as these are never required and should simply be avoided. The default value for the variable's type can be used in place of any self-reference. This is what the compiler will do in any case.

In many languages, such self-references are not even allowed. The Java compiler, for example, will complain:

  public class SelfRef {
    String s = s + s;
  
  public static void main(String[] args) {     System.out.println(new SelfRef().s.length());   } }
$ javac SelfRef.java SelfRef.java:2: error: self-reference in initializer   String s = s + s;              ^ SelfRef.java:2: error: self-reference in initializer   String s = s + s;                  ^ 2 errors

Self-reference checking in Java is not very sophisticated, however, and is easily fooled:

  public class SelfRef2 {
    String s = this.s + this.s; // fool the compiler
  
  public static void main(String[] args) {     System.out.println(new SelfRef2().s.length());   } }
$ javac SelfRef2.java
$ java SelfRef2 8
image images/moralgraphic117px.png Avoid self-referential variable definitions: replace occurrences of the variable being defined with the default value for the variable's type.

Footnotes for Chapter 13:

[1] Odersky, The Scala Language Specification, Section 4.1. [Ode14]

[2] Lindholm, et. al., The Java Virtual Machine Specification, Section 15.18.1.1. [Lin13]

[3] See the Javadoc for StringBuilder. [Ora]

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.227.111.208