我有一个像
这样的签名的宏def generateSomething[A] = macro ...
也就是说,它接受一个类型参数。该类型应该是一个case类,因此它的伴随对象中始终有相应的apply
方法。
此宏在其他所有内容中生成此apply
方法的调用,例如,对于此类:
case class A(x: Int, y: String)
将生成以下调用:
A.apply(someFunction[Int], someFunction[String])
我从someFunction
签名中提取apply
来电的参数类型。
除非参数化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])
而不是上面的那一块。有可能吗?
更新
我想我应该重新提出这个问题。
假设有一个类
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
。例如(使用上面的类):
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
参数类型可以依赖于类型参数。虽然宏已知这些参数(因为它总是用具体类型调用),但我不知道如何通过"传递"这些参数"通过"到apply
函数,以使preprocess
类型参数正确。
更新2
Here是我目前所拥有的。
答案 0 :(得分:1)
类似的东西:
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)")
}
}
所以你只是用你的“实际类型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])
答案 1 :(得分:-1)
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])
同样,由于这是伪代码,您的里程可能会有所不同,但对宏的签名(以及可能应用实现)应用一些统一性可能会对您的实施有很大帮助。