使用 Witness 提取类型标签

Extracting type tag with Witness

我正在 运行 对 Witness 进行一些实验,现在正试图了解它是如何工作的。考虑以下示例:

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

正如预期的那样,程序打印

xxx
yyy

我试图了解这些 xxxyyy 值是如何获得的,并打印了 main 函数的实际字节码:

    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

可以看出,所有这些 Witness-tricks 都没有呈现。隐式 Witness.Aux[TypeTag] 来自 implicit def apply[T]: Witness.Aux[T] = macro SingletonTypeMacros.materializeImpl[T]

它实际上是如何工作的?是编译器优化了那些方法调用吗?或者这是通过 macro?

完成的

在方法调用getTaggedValue("xxx" ->> 32)中,结果类型是单例类型"xxx"。 Scala 2.12 编译器原则上会将每个具有单例类型的表达式内联到伴随值(由于缺乏更好的术语,"xxx" 类型的 "value" 是字符串 "xxx")。所以在这种情况下,整个表达式 getTaggedValue("xxx" ->> 32) 及其包含的所有内容,如构造 "xxx" ->> 32 和调用隐式 Witness,都将被删除为简单值 "xxx".

Scala 2.13 对单例类型进行了一些修改,使它们成为语言中的第一个 class 公民,并且在该过程中,内联变得不那么激进了。因为如果您要向 getTaggedValue 方法添加副作用,您会发现这种内联并不总是安全的。这意味着当您使用 Scala 2.13 进行编译时,您 在字节码中看到诸如 getTaggedValueWitness.mkWitness 之类的调用。