在插入之前将字符串插入quasiquote(运行时quasiquote?)

时间:2016-12-11 14:49:14

标签: scala reflection scala-quasiquotes

我试图为接受case类值的函数生成Tree,并在给定位置返回case类参数的值。这对于提取私有参数的值非常有用。

  import reflect.runtime.currentMirror
  import scala.reflect.runtime.universe._
  import scala.tools.reflect.ToolBox
  val tb = currentMirror.mkToolBox()

  case class A(private val first: Int)

  case class B(first: Int, private val second: Int)

  def get(tpe: Type, position: Option[Int]): Tree = {
    val pos = s"${position.map(p => s"._${p + 1}").getOrElse("")}"
    tb.parse(s"(a: $tpe) => $tpe.unapply(a).get$pos")
  }

  println(tb.eval(get(typeOf[A], None)).asInstanceOf[(A) => Int](A(1)))

  println(tb.eval(get(typeOf[B], Some(1))).asInstanceOf[(B) => Int](B(1, 2)))

我还添加了以下依赖项:

scalaVersion := "2.11.8"
libraryDependencies ++= Seq(
  "org.scala-lang" % "scala-reflect" % scalaVersion.value,
  "org.scala-lang" % "scala-compiler" % scalaVersion.value
)
当案例类只有一个参数时,

positionNone

我的解决方案正在运行,但我如何摆脱tb.parse(s"...")用quasiquote替换它 q"..."

我试过了,但它失败了:

Don't know how to unquote here
[error]     q"(a: $tpe) => $tpe.unapply(a).get$pos"
[error]                                        ^

据我所知,我无法在quasiquote中插入一些在运行时构造的字符串,q"..."在编译时解析,而不像tb.parse我是对的吗?

s"(a: $tpe) => $tpe.unapply(a).get$pos"那样进行插值也是安全的吗?使用q"..."语法时,quasiquote知道$tpeType,但字符串插值会从中生成一个字符串。我不确定这是否会在更复杂和具体的情况下发挥作用。

1 个答案:

答案 0 :(得分:1)

是对的。 Quasiquotes不能只是插入普通字符串,它们必须插入其他AST片段。如果您自己构建AST的一些片段,则可以将它们与q一起使用。因此,你可以这样写:

def getElem(tpe: Type, pos: Option[Int]): Tree = {
  // Make something like TermName("_N")
  val tupleAccess = pos.map("_" + _).map(TermName.apply)
  val tupleExpr = {
    val plain = q"${tpe.typeSymbol.companion}.unapply(a).get"
    // Build into $companion.unapply(a).get(._N)
    tupleAccess.foldLeft(plain)(Select.apply)
  }
  q"(a: $tpe) => $tupleExpr"
}

瞧!

object A { object B { object C { case class D(private val x: Int, val y: String) } } }
val all = tb.eval(getElem(typeOf[A.B.C.D], None)).asInstanceOf[A.B.C.D => (Int, String)]
val int = tb.eval(getElem(typeOf[A.B.C.D], Some(1))).asInstanceOf[A.B.C.D => Int]
val str = tb.eval(getElem(typeOf[A.B.C.D], Some(2))).asInstanceOf[A.B.C.D => String]
val ds = {
  val D = A.B.C.D
  List(D(1, "one"), D(2, "two"), D(3, "three"))
}
ds.map(all) == List((1, "one"), (2, "two"), (3, "three"))
ds.map(int) == List(1, 2, 3)
ds.map(str) == List("one", "two", "three")