I don’t have any major conclusions to share in this blog post, but ... what I was curious about is how Scala implements “lazy val” fields. That is, when the Scala code I write is translated into a .class file and bytecode that a JVM can understand, what does that resulting code look like?
A little `lazy val` conversion example
To look at this I created a file named Foo.scala and put the following code in it:
class Foo { lazy val foo = "foo " + "bar" }
I then compiled this code with this command:
$ scalac -Xprint:all Foo.scala
That command didn’t print out what I was looking for, so I then tried the javap
command on the resulting .class file:
$ javap Foo Compiled from "Foo.scala" public class Foo { public java.lang.String foo(); public Foo(); }
That also didn’t tell me too much, except it looks like the foo
field is converted to a method. So then I used the jad
command to decompile the .class file back to a Java file:
$ jad Foo import scala.runtime.BoxedUnit; public class Foo { private String foo$lzycompute() { synchronized(this) { if(!bitmap$0) { foo = "foo bar"; bitmap$0 = true; } BoxedUnit _tmp = BoxedUnit.UNIT; } return foo; } public String foo() { return bitmap$0 ? foo : foo$lzycompute(); } public Foo() { } private String foo; private volatile boolean bitmap$0; }
This is what I was looking for. It shows that there is a private foo
field, a public foo
method, and that foo
method calls a private method named foo$lzycompute
. The lazy
magic happens in both the foo
method and the foo$lzycompute
method. I’m not going to take the time to explain it, but if you know Java, you can see it in that code.
A second `lazy val` conversion example
Curious how this might grow, I added another line of code to my Scala source code:
class Foo { lazy val foo = "foo " + "bar" val baz = foo + " baz" }
I compiled this code with scalac
, then skipped the intermediate things I tried before and just decompiled the .class file with jad
, and got the following output from it:
import scala.collection.mutable.StringBuilder; import scala.runtime.BoxedUnit; public class Foo { private String foo$lzycompute() { synchronized(this) { if(!bitmap$0) { foo = "foo bar"; bitmap$0 = true; } BoxedUnit _tmp = BoxedUnit.UNIT; } return foo; } public String foo() { return bitmap$0 ? foo : foo$lzycompute(); } public String baz() { return baz; } public Foo() { } private String foo; private final String baz = (new StringBuilder()).append(foo()).append(" baz").toString(); private volatile boolean bitmap$0; }
The big change in that code is this line:
private final String baz = (new StringBuilder()).append(foo()).append(" baz").toString();
As a reminder, I defined baz
in my Scala file like this:
val baz = foo + " baz"
and that simple line of code resulted in the private final String baz
line of code shown.
One more `lazy val` conversion example
I was going to stop there, but okay, here’s one more example. First I created this Foo.scala file:
class Foo { lazy val text = io.Source.fromFile("/etc/passwd").getLines.foreach(println) }
Then I compiled it with scalac
and then decompiled it with jad
to get this Java source code:
import scala.Predef$; import scala.Serializable; import scala.collection.Iterator; import scala.io.*; import scala.runtime.AbstractFunction1; import scala.runtime.BoxedUnit; public class Foo { private void text$lzycompute() { synchronized(this) { if(!bitmap$0) { Source$.MODULE$.fromFile("/etc/passwd", Codec$.MODULE$.fallbackSystemCodec()).getLines().foreach(new Serializable() { public final void apply(Object x) { Predef$.MODULE$.println(x); } public final volatile Object apply(Object v1) { apply(v1); return BoxedUnit.UNIT; } public static final long serialVersionUID = 0L; } ); bitmap$0 = true; } BoxedUnit _tmp = BoxedUnit.UNIT; } } public void text() { if(!bitmap$0) text$lzycompute(); } public Foo() { } private BoxedUnit text; private volatile boolean bitmap$0; }
As you can see, the simple “wrapper” code outside of the text$lzycompute
method is very similar, but the text$lzycompute
method itself is quite different. Once again I’m not going to comment on that code because all I want to do today is to take a quick look at how this works. If/when I feel a little less lazy, I’ll write more about this.
this post is sponsored by my books: | |||
#1 New Release |
FP Best Seller |
Learn Scala 3 |
Learn FP Fast |
The end
As I mentioned at the beginning of this post, I don’t have any major conclusions or anything here. I just wanted to take a quick look at how the Scala lazy val
syntax gets converted into Java code and/or bytecode that the JVM can understand, and these examples are a start down this road.
Of course there are many more things you can do with the Scala lazy val
syntax, and while I’m not going to dig into that today, these examples show how you can do that if you’re interested in looking into it.