我正在尝试访问泛型类型的隐式参数。通过调用具有显式泛型类型的方法(例如下面的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]
}
答案 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)
}
}