2014-01-18 51 views
1

編輯:我發現我的錯誤 - 有在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)我們可以使用keyupdateMap中檢索適當的更新方法,然後應用更新使用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] 
+0

您應該然後再粘貼編輯作爲一個答案,關閉問題,所以顯然問題解決了。 –

回答

0

我解決了這一問題 - 原來在我recursiveMethods quasiquotes過的innerMap.toSeq(...)代替innerMap.toSeq.map(...) - 我忽視了在REPL第一測試代碼