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

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