如何在泛型方法中替换实际类型参数以获取其值参数的最终类型?

时间:2015-02-03 22:27:45

标签: scala generics scala-macros

我有一个像

这样的签名的宏
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是我目前所拥有的。

2 个答案:

答案 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])

同样,由于这是伪代码,您的里程可能会有所不同,但对宏的签名(以及可能应用实现)应用一些统一性可能会对您的实施有很大帮助。