2014-01-18 1 views
1

Edit: я нашел мою ошибку - произошла ошибка в quasiquotes для моего рекурсивного случая, который вызывает его вернуть искаженную последовательность«ошибка: отсутствует параметр типа» во время макросъемки сплайсинга


Я пытаюсь для создания макроса, который превратит класс case 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])), где ключи карты являются именами полей класса case - идея заключается в том, что с учетом JsObject("key" -> JsValue) мы можем получить соответствующий метод обновления от updateMap, используя key, а затем примените обновление, используя 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(...) - я забыл проверить код в РЕПЛ первой