編輯:我發現我的錯誤 - 有在quasiquotes我遞歸的情況下,錯誤是導致它返回一個畸形的序列「錯誤:缺少參數類型的」宏觀剪接過程中
我想創建一個宏,將案例類T
轉換爲updateMap: Map[String, (play.api.libs.json.JsValue) => Try[(T) => T]]
(How to use scala macros to create a function object (to create a Map[String, (T) => T])),其中映射的鍵是案例類的字段名稱 - 想法是給定JsObject("key" -> JsValue)
我們可以使用key
從updateMap
中檢索適當的更新方法,然後應用更新使用JsValue
。我有這個工作在非遞歸的情況下,即給出一個沒有任何其他案例類作爲領域的案例類。不過,我想,要擴大這個宏,以便它可以生成包含其他case類case類的updateMap
,例如
case class Inner(innerStr: String)
case class Outer(outerStr: String, inner: Inner)
updateMap[Outer] = {
// updateMap[Inner]
val innerMap = Map("innerStr" -> (str: String) =>
Try { (i: Inner) => i.copy(innerStr = str) })
// updateMap[Outer]
Map("outerStr" -> (str: String) =>
Try { (o: Outer) => o.copy(outerStr = str) },
"inner.innerStr" -> (str: String) =>
Try { (o: Outer) => innerMap.get("innerStr").get(str).flatMap(lens => o.copy(inner = lens(o.inner))) })}
換句話說給出updateMap[Outer]
,我就可以直接更新對象outerStr
字段,否則我將能夠更新對象的inner.innerStr
字段,無論是哪種情況都會返回Try[Outer]
。
代碼正常的非遞歸的情況下(copyMapRec[Inner]()
),但遞歸的情況下(copyMapRec[Outer]()
)是給我一個「錯誤:缺少參數類型」的錯誤 - 我假設我要麼需要提供一個隱含的參數在某處或者我對拼接有一個基本的誤解。
下面的代碼使用(String) => Try[(T) => T]
而不是(JsValue) => Try[(T) => T]
,因此我不需要將播放框架導入到我的REPL中。我使用隱式轉換將JsValue
(或String
)轉換爲適當的類型(這發生在val x: $fieldType = str
行的基準情況準引號中;如果適當的隱式轉換不可用,則會出現編譯器錯誤)。
import scala.language.experimental.macros
def copyMapImplRec[T: c.WeakTypeTag](c: scala.reflect.macros.Context)(blacklist: c.Expr[String]*): c.Expr[Map[String, (String) => scala.util.Try[(T) => T]]] = {
import c.universe._
// Fields that should be omitted from the map
val blacklistList: Seq[String] = blacklist.map(e => c.eval(c.Expr[String](c.resetAllAttrs(e.tree))))
def rec(tpe: Type): c.Expr[Map[String, (String) => scala.util.Try[(T) => T]]] = {
val typeName = tpe.typeSymbol.name.decoded
// All fields in the case class's primary constructor, minus the blacklisted fields
val fields = tpe.declarations.collectFirst {
case m: MethodSymbol if m.isPrimaryConstructor => m
}.get.paramss.head.filterNot(field => blacklistList.contains(typeName + "." + field.name.decoded))
// Split the fields into case classes and non case classes
val recursive = fields.filter(f => f.typeSignature.typeSymbol.isClass && f.typeSignature.typeSymbol.asClass.isCaseClass)
val nonRecursive = fields.filterNot(f => f.typeSignature.typeSymbol.isClass && f.typeSignature.typeSymbol.asClass.isCaseClass)
val recursiveMethods = recursive.map {
field => {
val fieldName = field.name
val fieldNameDecoded = fieldName.decoded
// Get the c.Expr[Map] for this case class
val map = rec(field.typeSignature)
// Construct an "inner.innerStr -> " seq of tuples from the "innerStr -> " seq of tuples
q"""{
val innerMap = $map
innerMap.toSeq.map(tuple => ($fieldNameDecoded + "." + tuple._1) -> {
(str: String) => {
val innerUpdate = tuple._2(str)
innerUpdate.map(innerUpdate => (outer: $tpe) => outer.copy($fieldName = innerUpdate(outer.$fieldName)))
}
})}"""
}
}
val nonRecursiveMethods = nonRecursive.map {
field => {
val fieldName = field.name
val fieldNameDecoded = fieldName.decoded
val fieldType = field.typeSignature
val fieldTypeName = fieldType.toString
q"""{
$fieldNameDecoded -> {
(str: String) => scala.util.Try {
val x: $fieldType = str
(t: $tpe) => t.copy($fieldName = x)
}.recoverWith {
case e: Exception => scala.util.Failure(new IllegalArgumentException("Failed to parse " + str + " as " + $typeName + "." + $fieldNameDecoded + ": " + $fieldTypeName))
}
}}"""
}
}
// Splice in all of the sequences of tuples, flatten the sequence, and construct a map
c.Expr[Map[String, (String) => scala.util.Try[(T) => T]]] {
q"""{ Map((List(..$recursiveMethods).flatten ++ List(..$nonRecursiveMethods)):_*) }"""
}
}
rec(weakTypeOf[T])
}
def copyMapRec[T](blacklist: String*) = macro copyMapImplRec[T]
您應該然後再粘貼編輯作爲一個答案,關閉問題,所以顯然問題解決了。 –