15

I have an object:

object A {
 val init = println("Hello")
}

I use it in a trait:

trait SomeTratit {
  val a = A.init
}

Then I use trait it in a class:

class SomeClass extends SomeTrait

When I instantiate SomeClass with new SomeClass I expect to see "Hello" in the console, but don't get it. Why?

Also I expect to see "Hello" only once while instantiating several objects, but don't see any "Hello" in console

5
  • Looks like a bug of Scala to me. Commented Jun 14, 2014 at 15:55
  • bug in Scala or in REPL? Tried this only in REPL Commented Jun 14, 2014 at 15:59
  • 2
    I can confirm it happens both inside and outside the REPL. It doesn't happen if you replace println("Hello") with { println("Hello"); 1 }, so it looks like an optimization for Unit gone awry. Commented Jun 14, 2014 at 16:06
  • @maks Post it on the issue tracker. Commented Jun 14, 2014 at 16:57
  • how I can get such behaviour, I mean performing some side effect only once when instantiating objects which mix in that trait without? I've tried to simply call A.init in the body of the trait, it works but compiler emits warning Commented Jun 15, 2014 at 11:17

5 Answers 5

6

I don't know if this should be considered a bug, but here's HOW this happened.

If you look at the generated bytecode of object A, it's like this:

public final class A$ {
  public static final A$ MODULE$;

  private final scala.runtime.BoxedUnit init;

  public static {};
    Code:
       0: new           #2                  // class A$
       3: invokespecial #12                 // Method "<init>":()V
       6: return

  public void init();
    Code:
       0: return

  private A$();
    Code:
       0: aload_0
       1: invokespecial #16                 // Method java/lang/Object."<init>":()V
       4: aload_0
       5: putstatic     #18                 // Field MODULE$:LA$;
       8: aload_0
       9: getstatic     #23                 // Field scala/Predef$.MODULE$:Lscala/Predef$;
      12: ldc           #25                 // String Hello
      14: invokevirtual #29                 // Method scala/Predef$.println:(Ljava/lang/Object;)V
      17: getstatic     #34                 // Field scala/runtime/BoxedUnit.UNIT:Lscala/runtime/BoxedUnit;
      20: putfield      #36                 // Field init:Lscala/runtime/BoxedUnit;
      23: return
}

You can find the println("Hello") you expected in the constructor of A$, but there's nothing in init(). This is perfectly correct because it is you intention that println("Hello") would not be executed every time when you called init(), right?

However, the problem comes in SomeClass:

public class SomeClass implements SomeTrait {
  public void a();
    Code:
       0: return

  public void SomeTrait$_setter_$a_$eq(scala.runtime.BoxedUnit);
    Code:
       0: return

  public SomeClass();
    Code:
       0: aload_0
       1: invokespecial #21                 // Method java/lang/Object."<init>":()V
       4: aload_0
       5: invokestatic  #27                 // Method SomeTrait$class.$init$:(LSomeTrait;)V
       8: return
}

What?! There's nothing in SomeClass.a(), either! But think about it, it's also perfectly reasonable: why would I bother calling it since there's literally nothing in A$.init() and it returns nothing (i.e. no field to be set)? Why not just optimize that out (Maybe Java did this optimization. Or Scala did. I don't know)? However, this optimization also erase the only appearance of A$, which means no constructor will be called for A$. That's why the Hello never appeared.

However, if you change the code a little bit so that the bytecode of init() will not be empty, like this:

object A {
  val init = { println("Hello"); 1 }
}

which compiles to the following bytecode:

public int init();
  Code:
     0: aload_0
     1: getfield      #17                 // Field init:I
     4: ireturn

in which case you will find the bytecode for SomeClass.a() like this:

public int a();
  Code:
     0: aload_0
     1: getfield      #15                 // Field a:I
     4: ireturn

where the field was set in SomeTrait$class:

public abstract class SomeTrait$class {
  public static void $init$(SomeTrait);
    Code:
       0: aload_0
       1: getstatic     #13                 // Field A$.MODULE$:LA$;
       4: invokevirtual #17                 // Method A$.init:()I
       7: invokeinterface #23,  2           // InterfaceMethod SomeTrait.SomeTrait$_setter_$a_$eq:(I)V
       12: return
}

A$.init() is called to set this field, so in this case you can expect Hello to appear.

Sign up to request clarification or add additional context in comments.

Comments

4

If you really need such side-effect in your code - you can see "hello" when accessing A directly (so just mention A in your trait). This is a feature of objects - they don't initialize while they don't really need to:

scala> object A {val a = println("hello"); val b = (); println("bye") }
defined module A

scala> A.b

scala> A.a

scala> A
hello
bye
res10: A.type = A$@1be2f5d8

You may notice simmilar behavior for type members:

scala> object A {println("init"); type T = Int }
defined module A

scala> val i: A.T = 5
i: A.T = 5

scala> A
init
res15: A.type = A$@73f5477e

scala>

3 Comments

This seems like complete madness.
@AndyHayden I think putting a block of side-effectful code returning Unit inside an immutable 'val' (aka constant) is a madness :) I don't know why compiler even allows that (instead of just forcing everyone to use regular inialization sections). Really, in 5 years I never needed something like this. If I really needed such delayed initialization (it wouldn't be for useless Unit though) - I'd use 'lazy val'
most painful thing in scala is its regular initialization, like guess what this code would print: object A{val a = b; val b= 5; println(a)}; A
2

Seems to me like it's related with the compiler optimization. For example, the code as given doesn't show the "Hello" message, because the compiler seems to infer that as A.init is of type Unit, the local val a of SomeTrait will always end up with the same constant value. So the compiler just assign it and that's it.

Nonetheless, if you make the evaluation of init not eager, by making the field init at Object A also lazy, then the message is actually printed:

object A {
  lazy val init = println("Hello")
}

Moreover, even if you just do the same but force the init function in Object A to return some meaningful value, then the message will also be printed, since the compiler cannot infer the resulting value to be constant.

  object A {
    val init = { println("Hello"); 1} // returns some integer
  }

  trait SomeTrait {
    val a = A.init
  }

  class SomeClass extends SomeTrait

This is my interpretation, but I can be missing something. Hope it helps.

Comments

0

When you initialize a in the trait all you did is copy a reference to function A.init into SomeTrait.a. You could then do a() to invoke the function and see the expected output.

Comments

-4

init is the type Unit, therefore assess the function is in vain.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.