如何在泛型方法中替换实际类型参数以获得其值参数的最终类型?
How to substitute actual type arguments in a generic method to obtain final types of its value arguments?
我有一个签名像
的宏
def generateSomething[A] = macro ...
也就是说,它接受一个类型参数。该类型应为 case class,因此它在其伴生对象中始终具有相应的 apply
方法。
这个宏,在其他所有东西中,生成了这个 apply
方法的调用,所以,例如,对于这个 class:
case class A(x: Int, y: String)
将生成以下调用:
A.apply(someFunction[Int], someFunction[String])
我从 apply
签名中提取 someFunction
调用的参数类型。
一切正常,除非 A
被参数化:
case class A[T](x: Int, y: T)
使用我当前的方法,为 generateSomething[A[String]]
生成以下内容:
A.apply[String](someFunction[Int], someFunction[T])
这显然是无效的。
但是,我不知道如何在已知所有类型参数后获取apply
的参数。也就是说,我不知道如何确保
generateSomething[A[String]]
生成
A.apply[String](someFunction[Int], someFunction[String])
而不是上面那一段。可能吗?
更新
我想我应该重新表述这个问题。
假设有一个class
case class A[T1, ..., Tn](x1: A1, ..., xm: Am)
其中 Ai
可以依赖于 Tk
的任意子集。示例:
// T1 = T
// A1 = Int, A2 = T
case class B[T](x: Int, y: T)
// T1 = U, T2 = V
// A1 = Map[U, V], A2 = List[V]
case class C[U, V](m: Map[U, V], l: List[V])
// T1 = W
// A1 = W, A2 = W
case class D[W](t: W, u: W)
// No Ts
// A1 = String, A2 = Double
case class E(v: String, w: Double) // no type parameters at all
我需要编写一个宏,它接受一个类型参数 A
并扩展为 A.apply
带有预处理参数的方法调用:
myMacro[A[U1, ..., Un]]
// expands to
A.apply[U1, ..., Un](preprocess[A1], ..., preprocess[An])
Uk
这里是代替 Tk
的实际类型参数。例如(使用上面的 classes):
myMacro[B[String]] -> B.apply[String](preprocess[Int], preprocess[String])
myMacro[C[Int, Double]] -> C.apply[Int, Double](preprocess[Map[Int, Double]], preprocess[List[Double]])
myMacro[D[Long]] -> D.apply[Long](preprocess[Long], preprocess[Long])
myMacro[E] -> D.apply(preprocess[String], preprocess[Double])
你看,apply
参数类型可以依赖于类型参数。虽然宏知道这些参数(因为它总是用具体类型调用),但我不知道如何 "pass" 这些参数 "through" 到 apply
函数以便 preprocess
输入正确的参数。
更新 2
Here 大致是我目前拥有的。
Scala macro docs 提供了一些见解:
import scala.reflect.macros.blackbox.Context
class Impl(val c: Context) {
def mono = c.literalUnit
def poly[T: c.WeakTypeTag] = c.literal(c.weakTypeOf[T].toString)
}
object Macros {
def mono = macro Impl.mono
def poly[T] = macro Impl.poly[T]
}
上面的poly
方法调用c.weakTypeOf
,它通过在编译时创建隐式来捕获擦除的类型信息。类似的模式可能有助于具体化您的宏。
编辑以上更新:
c.WeakTypeTag
在调用站点捕获类型信息。编译器需要满足我们应用于宏参数的类型界限,应用隐式转换从您的类型中捕获此信息。
我们的想法是,一旦您获得此信息,就可以使用它来获取未知类型的字符串,您可以在具体化期间使用它。
就类型参数数量的差异而言,我认为宏的参数数量必须独立于它所应用的类型才有意义,即根据你的最后一个块我而是会做这样的事情(假设他们都有两个字段 apply
):
myMacro[B, Int, String]] -> B.apply(preprocess[Int], preprocess[String])
myMacro[C, Map[Int,Double], List[Double]] -> C.apply(preprocess[Map[Int, Double]], preprocess[List[Double]])
myMacro[D, Long, Long] -> D.apply(preprocess[Long], preprocess[Long])
myMacro[E, String, Double] -> E.apply(preprocess[String], preprocess[Double])
同样,由于这是伪代码,您的里程可能会有所不同,但对宏的签名应用一些统一性(并且可能应用实现)可能会对您的实现有很大帮助。
类似于:
case class X[A](a: A)
object TParamMacro {
import scala.language.experimental.macros
import scala.reflect.macros.whitebox.Context
def m[A](): A = macro mImpl[A]
def mImpl[A: c.WeakTypeTag](c: Context)(): c.Expr[A] = {
import c.universe._
val TypeRef(pre, sym, args) = weakTypeTag[A].tpe
val t = args.head
val expr =
if (t <:< typeOf[String]) q"""X.apply[$t]("hi")"""
else if (t <:< typeOf[Int]) q"X.apply[$t](42)"
else q"X.apply[$t](null)"
c.Expr[A](expr)
}
}
object Test extends App {
Console println TParamMacro.m[X[String]]()
}
更多示例:
object TParamMacro {
import scala.language.experimental.macros
import scala.reflect.macros.whitebox.Context
def m[A](): Any = macro mImpl[A]
def mImpl[A: c.WeakTypeTag](c: Context)() = {
import c.universe._
val TypeRef(pre, sym, args) = weakTypeTag[A].tpe
val t = args.head
val expr = if (t <:< typeOf[String]) q"""X.apply[List[$t]](List.apply[$t]("hi"))"""
else if (t <:< typeOf[Int]) q"X.apply[List[$t]](List.apply[$t](42))"
else q"X.apply[List[$t]](Nil)"
expr
}
}
在哪里
Console println TParamMacro.m[X[String]]()
产量
X(List(hi))
修复要点进行编辑:
package evaluator
import scala.language.experimental.macros
import scala.reflect.macros.whitebox.Context
object Evaluator {
def preprocess[T]: T = ???
def evaluate[A]: Any = macro evaluateImpl[A]
def evaluateImpl[A: c.WeakTypeTag](c: Context): c.Expr[A] = {
import c.universe._
val tpe = weakTypeOf[A]
val sym = tpe.typeSymbol.asClass
require(sym.isCaseClass)
val companionSym = sym.companion
val companionTpe = companionSym.typeSignature
val applyMethod = companionTpe.member(TermName("apply")).asMethod
val paramTypes = applyMethod.paramLists.flatten.map(_.typeSignature)
Console println s"apply($paramTypes)"
val TypeRef(_, _, tpeTypeArgs) = tpe
val from = applyMethod.typeParams
val to = tpeTypeArgs
val arguments = paramTypes map { t =>
val u = if (from.nonEmpty) t.substituteTypes(from, to) else t
Console println s"param is $t, subst is $u"
q"evaluator.Evaluator.preprocess[$u]"
}
c.Expr(q"$companionSym.apply[..$tpeTypeArgs](..$arguments)")
}
}
因此,您只需将 "actual type args" 替换为方法的类型参数。在应用程序中对形式参数使用 "parameter" 对实际 arg 使用 "argument" 很有用。
样本:
package evaluator
case class A(x: Int, y: String)
case class B[T](x: Int, y: T)
case class C[U, T](x: Int, y: T, z: U)
object Test extends App {
Evaluator.evaluate[A]
Evaluator.evaluate[B[String]]
Evaluator.evaluate[C[String, List[Int]]]
}
正在使用
-Xprint:typer
然后
A.apply(evaluator.Evaluator.preprocess[Int], evaluator.Evaluator.preprocess[String]);
B.apply[String](evaluator.Evaluator.preprocess[Int], evaluator.Evaluator.preprocess[String]);
C.apply[String, List[Int]](evaluator.Evaluator.preprocess[Int], evaluator.Evaluator.preprocess[List[Int]], evaluator.Evaluator.preprocess[String])
我有一个签名像
的宏def generateSomething[A] = macro ...
也就是说,它接受一个类型参数。该类型应为 case class,因此它在其伴生对象中始终具有相应的 apply
方法。
这个宏,在其他所有东西中,生成了这个 apply
方法的调用,所以,例如,对于这个 class:
case class A(x: Int, y: String)
将生成以下调用:
A.apply(someFunction[Int], someFunction[String])
我从 apply
签名中提取 someFunction
调用的参数类型。
一切正常,除非 A
被参数化:
case class A[T](x: Int, y: T)
使用我当前的方法,为 generateSomething[A[String]]
生成以下内容:
A.apply[String](someFunction[Int], someFunction[T])
这显然是无效的。
但是,我不知道如何在已知所有类型参数后获取apply
的参数。也就是说,我不知道如何确保
generateSomething[A[String]]
生成
A.apply[String](someFunction[Int], someFunction[String])
而不是上面那一段。可能吗?
更新
我想我应该重新表述这个问题。
假设有一个class
case class A[T1, ..., Tn](x1: A1, ..., xm: Am)
其中 Ai
可以依赖于 Tk
的任意子集。示例:
// T1 = T
// A1 = Int, A2 = T
case class B[T](x: Int, y: T)
// T1 = U, T2 = V
// A1 = Map[U, V], A2 = List[V]
case class C[U, V](m: Map[U, V], l: List[V])
// T1 = W
// A1 = W, A2 = W
case class D[W](t: W, u: W)
// No Ts
// A1 = String, A2 = Double
case class E(v: String, w: Double) // no type parameters at all
我需要编写一个宏,它接受一个类型参数 A
并扩展为 A.apply
带有预处理参数的方法调用:
myMacro[A[U1, ..., Un]]
// expands to
A.apply[U1, ..., Un](preprocess[A1], ..., preprocess[An])
Uk
这里是代替 Tk
的实际类型参数。例如(使用上面的 classes):
myMacro[B[String]] -> B.apply[String](preprocess[Int], preprocess[String])
myMacro[C[Int, Double]] -> C.apply[Int, Double](preprocess[Map[Int, Double]], preprocess[List[Double]])
myMacro[D[Long]] -> D.apply[Long](preprocess[Long], preprocess[Long])
myMacro[E] -> D.apply(preprocess[String], preprocess[Double])
你看,apply
参数类型可以依赖于类型参数。虽然宏知道这些参数(因为它总是用具体类型调用),但我不知道如何 "pass" 这些参数 "through" 到 apply
函数以便 preprocess
输入正确的参数。
更新 2
Here 大致是我目前拥有的。
Scala macro docs 提供了一些见解:
import scala.reflect.macros.blackbox.Context
class Impl(val c: Context) {
def mono = c.literalUnit
def poly[T: c.WeakTypeTag] = c.literal(c.weakTypeOf[T].toString)
}
object Macros {
def mono = macro Impl.mono
def poly[T] = macro Impl.poly[T]
}
上面的poly
方法调用c.weakTypeOf
,它通过在编译时创建隐式来捕获擦除的类型信息。类似的模式可能有助于具体化您的宏。
编辑以上更新:
c.WeakTypeTag
在调用站点捕获类型信息。编译器需要满足我们应用于宏参数的类型界限,应用隐式转换从您的类型中捕获此信息。
我们的想法是,一旦您获得此信息,就可以使用它来获取未知类型的字符串,您可以在具体化期间使用它。
就类型参数数量的差异而言,我认为宏的参数数量必须独立于它所应用的类型才有意义,即根据你的最后一个块我而是会做这样的事情(假设他们都有两个字段 apply
):
myMacro[B, Int, String]] -> B.apply(preprocess[Int], preprocess[String])
myMacro[C, Map[Int,Double], List[Double]] -> C.apply(preprocess[Map[Int, Double]], preprocess[List[Double]])
myMacro[D, Long, Long] -> D.apply(preprocess[Long], preprocess[Long])
myMacro[E, String, Double] -> E.apply(preprocess[String], preprocess[Double])
同样,由于这是伪代码,您的里程可能会有所不同,但对宏的签名应用一些统一性(并且可能应用实现)可能会对您的实现有很大帮助。
类似于:
case class X[A](a: A)
object TParamMacro {
import scala.language.experimental.macros
import scala.reflect.macros.whitebox.Context
def m[A](): A = macro mImpl[A]
def mImpl[A: c.WeakTypeTag](c: Context)(): c.Expr[A] = {
import c.universe._
val TypeRef(pre, sym, args) = weakTypeTag[A].tpe
val t = args.head
val expr =
if (t <:< typeOf[String]) q"""X.apply[$t]("hi")"""
else if (t <:< typeOf[Int]) q"X.apply[$t](42)"
else q"X.apply[$t](null)"
c.Expr[A](expr)
}
}
object Test extends App {
Console println TParamMacro.m[X[String]]()
}
更多示例:
object TParamMacro {
import scala.language.experimental.macros
import scala.reflect.macros.whitebox.Context
def m[A](): Any = macro mImpl[A]
def mImpl[A: c.WeakTypeTag](c: Context)() = {
import c.universe._
val TypeRef(pre, sym, args) = weakTypeTag[A].tpe
val t = args.head
val expr = if (t <:< typeOf[String]) q"""X.apply[List[$t]](List.apply[$t]("hi"))"""
else if (t <:< typeOf[Int]) q"X.apply[List[$t]](List.apply[$t](42))"
else q"X.apply[List[$t]](Nil)"
expr
}
}
在哪里
Console println TParamMacro.m[X[String]]()
产量
X(List(hi))
修复要点进行编辑:
package evaluator
import scala.language.experimental.macros
import scala.reflect.macros.whitebox.Context
object Evaluator {
def preprocess[T]: T = ???
def evaluate[A]: Any = macro evaluateImpl[A]
def evaluateImpl[A: c.WeakTypeTag](c: Context): c.Expr[A] = {
import c.universe._
val tpe = weakTypeOf[A]
val sym = tpe.typeSymbol.asClass
require(sym.isCaseClass)
val companionSym = sym.companion
val companionTpe = companionSym.typeSignature
val applyMethod = companionTpe.member(TermName("apply")).asMethod
val paramTypes = applyMethod.paramLists.flatten.map(_.typeSignature)
Console println s"apply($paramTypes)"
val TypeRef(_, _, tpeTypeArgs) = tpe
val from = applyMethod.typeParams
val to = tpeTypeArgs
val arguments = paramTypes map { t =>
val u = if (from.nonEmpty) t.substituteTypes(from, to) else t
Console println s"param is $t, subst is $u"
q"evaluator.Evaluator.preprocess[$u]"
}
c.Expr(q"$companionSym.apply[..$tpeTypeArgs](..$arguments)")
}
}
因此,您只需将 "actual type args" 替换为方法的类型参数。在应用程序中对形式参数使用 "parameter" 对实际 arg 使用 "argument" 很有用。
样本:
package evaluator
case class A(x: Int, y: String)
case class B[T](x: Int, y: T)
case class C[U, T](x: Int, y: T, z: U)
object Test extends App {
Evaluator.evaluate[A]
Evaluator.evaluate[B[String]]
Evaluator.evaluate[C[String, List[Int]]]
}
正在使用
-Xprint:typer
然后
A.apply(evaluator.Evaluator.preprocess[Int], evaluator.Evaluator.preprocess[String]);
B.apply[String](evaluator.Evaluator.preprocess[Int], evaluator.Evaluator.preprocess[String]);
C.apply[String, List[Int]](evaluator.Evaluator.preprocess[Int], evaluator.Evaluator.preprocess[List[Int]], evaluator.Evaluator.preprocess[String])