【问题标题】:How to substitute actual type arguments in a generic method to obtain final types of its value arguments?如何在泛型方法中替换实际类型参数以获得其值参数的最终类型?
【发布时间】:2015-02-05 07:33:03
【问题描述】:

我有一个带有类似签名的宏

def generateSomething[A] = macro ...

也就是说,它接受一个类型参数。该类型应该是一个案例类,因此它的伴生对象中总是有相应的apply 方法。

除其他外,此宏会生成此apply 方法的调用,例如,对于此类:

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

而不是上面的部分。有可能吗?

更新

我想我应该重新提出这个问题。

假设有一个类

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 大致就是我目前拥有的。

【问题讨论】:

    标签: scala generics scala-macros


    【解决方案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)")
      }
    }
    

    因此,您只需将“实际类型参数”替换为方法的类型参数。在应用程序中将“参数”用于形式参数,将“参数”用于实际 arg 很有用。

    示例:

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

    【讨论】:

    • 抱歉,我看不出它有什么帮助。请看我的更新。我需要一种通用机制来处理具有任意类型参数和任意数量值参数的案例类。
    • $t 是类型 arg,显示在一个准引用的应用程序中。您可以随意使用 args 列表。在您的编辑中,将List.apply 替换为preprocess,这就是我给出的示例。或者,向我们展示您的尝试,我们会告诉您您做错了什么。
    • 您的程序专门用于X 类型,该类型具有单类型参数和单值参数。我需要使用具有任意数量的类型和类型参数的类型。好的,我很快就会提供我的代码。
    • Here 是我目前拥有的。您可以看到它对 case class A(x: Int, y: String) 等具体类型和case class B[T](x: Int, y: T) 等泛型类型的工作原理。
    • 我认为这很棘手,因为 API 命名空间非常扁平。每天都做这些事情的人可能认为这很容易。正如他们所说,我只见树木不见树木。
    【解决方案2】:

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

    同样,由于这是伪代码,您的使用范围可能会有所不同,但对宏的签名应用一些统一性(并且可能应用实现)可能会对您的实现有很大帮助。

    【讨论】:

    • 谢谢,但我不明白这有什么帮助。请阅读我的更新。
    • 感谢您的更新,但很遗憾,我不确定我是否理解您的意思。 myMacro 应该适用于具有任意类型参数的任意类型,并且它只有一个参数。 apply 参数的个数也不依赖于原始类型的类型参数个数。
    • 我认为您可能需要为每个要支持的 arity 实现宏。这将是很多工作。
    • 我不能这样做,因为有问题的宏是预先未知类型的序列化器的物化器。此外,该类型的伴生对象可以具有任意的apply 方法,因此在调用站点没有关于apply 参数类型的信息。而且我仍然不相信将 apply 类型参数与其参数类型联系起来会有所帮助。
    • 你应该看看 shapeless 库,该库中的 Generic 类型基本上将类实例转换为一种列表形式,你可以将 polymorphic methods 应用于列表。然后您可以转换回原始类型。有关示例,请参阅此 gist
    猜你喜欢
    • 2015-06-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-15
    • 1970-01-01
    • 1970-01-01
    • 2013-09-13
    相关资源
    最近更新 更多