在运行时显式访问隐式参数

时间:2016-10-04 20:53:22

标签: scala generics implicit

我正在尝试访问泛型类型的隐式参数。通过调用具有显式泛型类型的方法(例如下面的printGenericType[Person]),Scala能够在简单的情况下找到隐含的正常情况。

但是,如果我创建TypeTag[Person]并将其传递给printGenericTypeGivenTypeTag,则Scala无法找到隐含参数。

case class Person(name: String)
case class Animal(age: Int)
implicit val p = Person("cory")
implicit val a = Animal(2)

def main(args: Array[String]): Unit = {
  // Can find the implicit Person, prints "Hello Person(cory)"
  printGenericType[Person]

  // Can find the implicit Animal, prints "Hello Animal(2)"
  printGenericType[Animal]

  // See comment below
  printNamedType("Person")
  printNamedType("Animal")
}

def printGenericType[T](implicit t: T) = {
  println(s"Hello $t")
}

def printGenericTypeGivenTypeTag[T](typeTag: TypeTag[T])(implicit t: T) = {
  println(s"Hello $t")
}

def printNamedType[T](name: String) = {
  val typetag: TypeTag[T] = getTypeTag[T](name)

  // Cannot find the implicit of type T, compiler error
  printGenericTypeGivenTypeTag(typetag)
}

def getTypeTag[T](name:String): TypeTag[T] = ... //Implementation irrelevant

如果我理解正确,Scala会在编译时找到隐式参数,因此在编译时它无法为泛型类型T找到隐式参数。

但是,我知道T的隐式实例确实存在。是否可以以找到printGenericTypeGivenTypeTag的隐含值的方式重写T?在运行时,该方法可以访问T的实际类型,因此它似乎应该能够找到范围内相同类型的隐式参数。

对于好奇,这背后的原因是为了避免这种情况:

name match {
  case "Person" => printGenericType[Person]
  case "Animal" => printGenericType[Animal]
}

1 个答案:

答案 0 :(得分:1)

回答问题

你真的不想隐含地传递T,而是TypeTag,而不是T。这就是我的意思,你可能会更好地使用隐式值类。

implicit class GenericPrinter[T](val obj: T) extends AnyVal {
  def printGenericType()(implicit tag: TypeTag[T]) = {
    // do stuff with the tag
    Console.println("Hello $obj")
  }
}
val x: Person = Person(...)
x.printGenericType

现在解决实际问题

如果您正在尝试打印案例类,我可能会进入隐式宏路径以增加方便性。编写一个为我们执行此操作的宏非常简单,例如根据任意case class的所有构造函数参数输出调试字符串。

trait DeepPrinter[T <: Product with  Serializable] {

  /**
    * Prints a deeply nested debug string for a given case class.
    * This uses implicit macros to materialise the printer type class.
    * In English, when we request for an implicit printer: DeepPrinter[T],
    * the pre-defined compile time macro will generate this method for us
    * based on the fields of the given case class.
    *
    * @param sep A separator to use to delimit the rows in a case class.
    * @return A fully traced debug string so we can see how a case class looks like.
    */
  def debugString(sep: String = "\n"): String
}

object DeepPrinter {
  implicit def deepPrinter[T <: Product with Serializable] = macro DeepPrinterImpl.deepPrinterImpl[T]
}

这个宏非常简单,看起来有点像这样。

import language.experimental.macros
import scala.reflect.macros.blackbox

@macrocompat.bundle
class DeepPrinterImpl(val c: blackbox.Context) {

  import c.universe._

  object CaseField {
    def unapply(symbol: TermSymbol): Option[(Name, Type)] = {
      if (symbol.isVal && symbol.isCaseAccessor) {
        Some(symbol.name -> symbol.typeSignature)
      } else {
        None
      }
    }
  }

  def fields(tpe: Type): Iterable[(Name, Type)] = {
    tpe.decls.collect { case CaseField(nm, tpeSn) => nm -> tpeSn }
  }

  def materialize[T : c.WeakTypeTag]: c.Expr[DeepPrinter[T]] = {
    val tpe = weakTypeOf[T]
    val (names, types) = fields(tpe).unzip

    // change the package name to the correct one here!
    val tree = q"""
       new com.bla.bla.DeepPrinter[$tpe] {
         def debugString(sep: String = "\n") = Seq(..$names).mkString(sep)
       }
     """
    c.Expr[DeepPrinter[T]](tree)
  }
}