我正在尝试使用Scala宏将无类型的Map[String, Any]
类表达式转换为它们对应的有类型case类表达式。
以下scala宏(几乎)可以完成工作:
trait ToTyped[+T] {
def apply(term: Any): T
}
object TypeConversions {
// At compile-time, "type-check" an untyped expression and convert it to
// its appropriate typed value.
def toTyped[T]: ToTyped[T] = macro toTypedImpl[T]
def toTypedImpl[T: c.WeakTypeTag](c: Context): c.Expr[ToTyped[T]] = {
import c.universe._
val tpe = weakTypeOf[T]
if (tpe <:< typeOf[Int] || tpe <:< typeOf[String]) {
c.Expr[ToTyped[T]](
q"""new ToTyped[$tpe] {
def apply(term: Any): $tpe = term.asInstanceOf[$tpe]
}""")
} else {
val companion = tpe.typeSymbol.companion
val maybeConstructor = tpe.decls.collectFirst {
case m: MethodSymbol if m.isPrimaryConstructor => m
}
val constructorFields = maybeConstructor.get.paramLists.head
val subASTs = constructorFields.map { field =>
val fieldName = field.asTerm.name
val fieldDecodedName = fieldName.toString
val fieldType = tpe.decl(fieldName).typeSignature
q"""
val subTerm = term.asInstanceOf[Map[String, Any]]($fieldDecodedName)
TypeConversions.toTyped[$fieldType](subTerm)
"""
}
c.Expr[ToTyped[T]](
q"""new ToTyped[$tpe] {
def apply(term: Any): $tpe = $companion(..$subASTs)
}""")
}
}
}
使用上面的toTyped
函数,我可以将例如一个无类型的人员值转换为其相应的有类型的Person
案例类:
object TypeConversionTests {
case class Person(name: String, age: Int, address: Address)
case class Address(street: String, num: Int, zip: Int)
val untypedPerson = Map(
"name" -> "Max",
"age" -> 27,
"address" -> Map("street" -> "Palm Street", "num" -> 7, "zip" -> 12345))
val typedPerson = TypeConversions.toTyped[Person](untypedPerson)
typedPerson shouldEqual Person("Max", 27, Address("Palm Street", 7, 12345))
}
但是,当尝试在通用Scala代码中从上方使用toTyped
宏时,出现了我的问题。假设我有一个使用indirection
宏的通用函数toTyped
:
object CanIUseScalaMacrosAndGenerics {
def indirection[T](value: Any): T = TypeConversions.toTyped[T](value)
import TypeConversionTests._
val indirectlyTyped = indirection[Person](untypedPerson)
indirectlyTyped shouldEqual Person("Max", 27, Address("Palm Street", 7, 12345))
在这里,我从toTyped
宏中收到一个编译时错误,抱怨类型T
尚未用具体类型实例化。我认为该错误的原因是,从toTyped
内部的indirection
的角度来看,类型T
仍然是通用的,尚未推断为Person
。因此,当通过Person
调用时,宏无法建立相应的indirection
案例类。但是,从呼叫站点indirection[Person](untypedPerson)
的角度来看,我们有T == Person
,所以我想知道是否有一种方法可以获取T
的实例化类型(即{{1} })放在宏Person
中。
以不同的方式输入:我可以将Scala宏toTyped
与泛型函数toTyped
组合在一起,但仍能够找出{{1}中类型参数indirection
的实例化类型。 }宏?还是我在这里没有希望,没有办法将Scala宏和泛型相结合?在后一种情况下,我想知道这里唯一的解决方案是否是将宏用法推到目前为止,以至于我可以将其实例化为T
而不是toTyped
。
非常感谢任何见解。谢谢! :-)
答案 0 :(得分:0)
需要扩展宏。每次使用主体为宏的函数时,Scala都必须生成代码并将其放在那里。正如您所怀疑的那样,这是非常具体的,与参数多态性的思想相矛盾,在这种情况下,您编写的代码独立于有关类型的特定知识。
当您想要一个通用(参数)定义和算法某些部分的多个每个类型的实现时,类型类是解决一般问题的方法之一。基本上,定义一些您可以考虑的接口(很可能需要遵循某种约定(以OOP术语来讲)),然后将此接口作为参数传递:
// example
trait SpecificPerType[T] {
def doSomethingSpecific(t: T): String
}
val specificForString: SpecificPerType[String] = new SpecificPerType[String] {
def doSomethingSpecific(t: String): String = s"MyString: $t"
}
val specificForInt: SpecificPerType[Int] = new SpecificPerType[Int] {
def doSomethingSpecific(t: Int): String = s"MyInt: $t"
}
def genericAlgorithm[T](values: List[T])(specific: SpecificPerType[T]): String =
values.map(specific.doSomethingSpecific).mkString("\n")
genericAlgorithm(List(1,2,3))(specificForInt)
genericAlgorithm(List("a","b","c"))(specificForString)
如您所见,传递此特定部分会很烦人,这是引入隐式的原因之一。
因此您可以使用如下隐式代码来编写它:
implicit val specificForString: SpecificPerType[String] = new SpecificPerType[String] {
def doSomethingSpecific(t: String): String = s"MyString: $t"
}
implicit val specificForInt: SpecificPerType[Int] = new SpecificPerType[Int] {
def doSomethingSpecific(t: Int): String = s"MyInt: $t"
}
def genericAlgorithm[T](values: List[T])(implicit specific: SpecificPerType[T]): String =
values.map(specific.doSomethingSpecific).mkString("\n")
/* for implicits with one type parameter there exist a special syntax
allowing to express them as if they were type constraints e.g.:
def genericAlgorithm[T: SpecificPerType](values: List[T]): String =
values.map(implicitly[SpecificPerType[T]].doSomethingSpecific).mkString("\n")
implicitly[SpecificPerType[T]] is a summoning that let you access implicit
by type, rather than by its variable's name
*/
genericAlgorithm(List(1,2,3)) // finds specificForString using its type
genericAlgorithm(List("a","b","c")) // finds specificForInt using its type
如果您使用宏生成该特征实现,则将能够使用通用算法,例如:
implicit def generate[T]: SpecificPerType[T] =
macro SpecificPerTypeMacros.impl // assuming that you defined this macro there
据我所知,这(将宏提取到类型类中)是一种常见的模式 能够同时使用宏生成一些代码,但仍在其之上构建逻辑 使用常规的参数代码。
(请注意:我不认为类型类的作用是作为宏生成代码的载体而受到限制的。)