Как использовать макросы scala для создания объекта функции (для создания карты [String, (T) => T])


Я пытаюсь использовать макросы Scala для создания карты класса case однопараметрических методов copy, причем каждый метод принимает Play Json JsValue и экземпляр класса case и возвращает обновленную копию экземпляра. Однако у меня возникают проблемы с синтаксисом макросов для возврата объекта функции.

Задан класс случаев

case class Clazz(id: Int, str: String, strOpt: Option[String])

Цель состоит в том, чтобы создать карту методов копирования класса

implicit def jsonToInt(json: JsValue) = json.as[Int]
implicit def jsonToStr(json: JsValue) = json.as[String]
implicit def jsonToStrOpt(json: JsValue) = json.asOpt[String]

Map("id" -> (json: JsValue, clazz: Clazz) = clazz.copy(id = json),
  "str" -> (json: JsValue, clazz: Clazz) = clazz.copy(str = json), ...)

Я нашел два связанных вопроса:

Использование макросы для создания карты полей класса case: Scala макросы: создание карты из полей класса в Scala

Доступ к методу копирования класса case с помощью макроса: как моделировать именованные параметры в вызовах методов с помощью макросов Scala?

...но я застрял на том, как я могу создать объект функции, чтобы я мог вернуть Map[String, (JsValue, T) => T]


Edit: благодаря предложению Евгения Бурмако использовать quasiquotes - вот где я сейчас использую Scala 2.11.0-M7, базируясь мой код на посте Джонатана Чоу (я переключился с использования (T, JsValue) = > T на (T, String) = > T, чтобы упростить мой импорт REPL)

Edit2: теперь включение $ TPE сплайсинга

import scala.language.experimental.macros

implicit def strToInt(str: String) = str.toInt

def copyMapImpl[T: c.WeakTypeTag](c: scala.reflect.macros.Context): 
    c.Expr[Map[String, (T, String) => T]] = {

  import c.universe._

  val tpe = weakTypeOf[T]

  val fields = tpe.declarations.collectFirst {
    case m: MethodSymbol if m.isPrimaryConstructor => m
  }.get.paramss.head

  val methods = fields.map { field => {
    val name = field.name
    val decoded = name.decoded
    q"{$decoded -> {(t: $tpe, str: String) => t.copy($name = str)}}"
  }}

  c.Expr[Map[Sring, (T, String) => T]] {
    q"Map(..$methods)"
  }
}

def copyMap[T]: Map[String, (T, String) => T] = macro copyMapImpl[T]

case class Clazz(i: Int, s: String)

copyMap[Clazz]
1 3

1 ответ:

Вы получили почти все правильно в своем коде, за исключением того, что вам нужно соединить T в квазиквоту, то есть написать $tpe вместо просто T.

Чтобы это выглядело более естественно, я обычно явно объявляю доказательства тегов типа в макросах, например def foo[T](c: Context)(implicit T: c.WeakTypeTag[T]) = .... После этого я просто пишу $T, и это выглядит почти нормально:)

Вы можете спросить, почему квазиквоты не могут просто выяснить, что в том месте, где они написаны, T ссылается на параметр типа макроса, а затем автоматически соедините его. Вообще-то, это был бы очень разумный вопрос. В таких языках, как Racket и Scheme, квазиквоты достаточно умны, чтобы помнить вещи о лексическом контексте, в котором они написаны, но в Scala это немного сложнее, потому что в языке так много различных областей. Тем не менее, есть план, чтобы добраться туда, и исследования в этом направлении уже ведутся: https://groups.google.com/forum/#! topic / scala-language/7h27npd1DKI .