Consider the following three code blocks (code cells) executed in Ammonite REPl with CodeClassWrapper
:
cmd0
:
val x = 123; val y = 456
cmd1
:
val z = 789
def foo(a: Int) = { x }
cmd2
:
val lambda = (a: Int) => a + foo(a) + z
In this example, cmd1
references variable x
defined in cmd0
and cmd2
uses variables from both cmd1
and cmd0
(transitive dependency cmd1#foo -> cmd0#x). Ammonite will translate these code blocks to this generated code (slightly simplified for better readability):
cmd0
:
object cmd0{
val wrapper = new cmd0; val instance = new wrapper.Helper
}
final class cmd0 extends java.io.Serializable {
final class Helper extends java.io.Serializable{
val x = 123; val y = 456
}
}
cmd1
:
object cmd1{
val wrapper = new cmd1; val instance = new wrapper.Helper
}
final class cmd1 extends java.io.Serializable {
val cmd0: ammonite.$sess.cmd0.instance.type = ammonite.$sess.cmd0.instance
import cmd0.{y, x}
final class Helper extends java.io.Serializable{
def foo(a: Int) = { x };
val z = 789;
}
}
cmd2
:
object cmd2{
val wrapper = new cmd2; val instance = new wrapper.Helper
}
final class cmd2 extends java.io.Serializable {
val cmd0: ammonite.$sess.cmd1.instance.type = null
val cmd1: ammonite.$sess.cmd1.instance.type = ammonite.$sess.cmd1.instance
import cmd1.{z, foo}
import cmd1.{y, x}
final class Helper extends java.io.Serializable{
val w = new NonSerializable
val lambda = (a: Int) => a + foo(a) + z
}
}
Note: real command code is more complex. In the example above, cmd2#cmd0 == null
because cmd0
is not directly referenced from cmd2
. However, actual Ammonite codegen will figure this out in runtime instead of codegen time. It doesn't impact ideas described here.
The picture below displays a reference graph for given code example. Dashed arrows are references that are not actually accessed (and are safe to null out). Please note that there's alsoa circular dependency between lambda
and cmd2$Helper
objects which may lead to serialization issues if not handled properly