I'm running some experiments with Witness
and now trying to understand how it works. Consider the following example:
import shapeless.syntax.singleton._
import shapeless.labelled.FieldType
import shapeless.Witness
def main(args : Array[String]): Unit = {
println(getTaggedValue("xxx" ->> 32))
println(getTaggedValue("yyy" ->> 44))
}
def getTaggedValue[TypeTag, Value](kt: FieldType[TypeTag, Value])
(implicit witness: Witness.Aux[TypeTag]): TypeTag = witness.value
As expected, the program prints
xxx
yyy
I tried to understand how these xxx
and yyy
values were gotten and printed the actual bytecode of the main
function:
62: getstatic #69 // Field scala/Predef$.MODULE$:Lscala/Predef$;
65: ldc #71 // String xxx
67: invokevirtual #75 // Method scala/Predef$.println:(Ljava/lang/Object;)V
70: getstatic #69 // Field scala/Predef$.MODULE$:Lscala/Predef$;
73: ldc #77 // String yyy
75: invokevirtual #75 // Method scala/Predef$.println:(Ljava/lang/Object;)V
As can be seen, all of these Witness
-tricks are not presented. The implicit Witness.Aux[TypeTag]
came from
implicit def apply[T]: Witness.Aux[T] = macro SingletonTypeMacros.materializeImpl[T]
How does it actually work? Is it the compiler who optimized those method invocation? Or this was done via macro
?
In the method call getTaggedValue("xxx" ->> 32)
, the result type is the singleton type "xxx"
. The Scala 2.12 compiler will in principle inline every expression with a singleton type to the accompanying value (for lack of better terminology, the "value" of type "xxx"
is the string "xxx"
). So in this case the entire expression getTaggedValue("xxx" ->> 32)
and everything it encompasses,like constructing "xxx" ->> 32
and summoning the implicit Witness
, will all be erased to the simple value "xxx"
.
Singleton types were reworked a bit for Scala 2.13 to make them first-class citizens in the language, and in that process inlining has been made less aggressive. Because if you would add side-effects to your getTaggedValue
method you will see that this inlining is not always safe. Which means that when you compile with Scala 2.13 you will see calls such as getTaggedValue
and Witness.mkWitness
in the bytecode.