Квазицитаты для нескольких параметров и списков параметров
Квазицитаты удивительны - они делают написание макросов в Scala чрезвычайно менее болезненным, и по моему опыту они почти всегда работают точно так, как я ожидал. И что самое приятное, они теперь доступны в виде плагина в Scala 2.10.
Этот вопрос о небольшой проблеме, с которой я столкнулся при написании этого поста. Это в моем списке вещей, чтобы посмотреть, когда я найду несколько минут, но я решил опубликовать это здесь, на случай, если кто-то другой может побить меня, и помочь другим людям, которые сталкиваются с тем же вопросом.
Предположим, у меня есть список списков пар имя-тип:
val pss = List(
List(newTermName("x") -> typeOf[Int], newTermName("y") -> typeOf[Char]),
List(newTermName("z") -> typeOf[String])
)
Я хочу превратить их в дерево, которое выглядит так:
def foo(x: Int, y: Char)(z: String) = ???
Следующее работает просто отлично:
q"def bar(${pss.head.head._1}: ${pss.head.head._2}) = ???"
То есть он строит следующее дерево:
def bar(x: Int) = ???
Что говорит о том, что я должен быть в состоянии написать что-то вроде этого:
val quoted = pss.map(_.map { case (n, t) => q"$n: $t" })
q"def foo..${quoted.map(ps => q"($ps)")} = 1"
Или немного проще, с несколькими параметрами в одном списке параметров:
q"def baz(..${quoted.head}) = ???"
Ни одна из них не работает - я получаю такие ошибки:
<console>:28: error: type mismatch;
found : List[c.universe.Typed]
required: List[c.universe.ValDef]
q"def baz(..${quoted.head}) = ???"
^
Достаточно справедливо - я могу видеть, как это будет выглядеть квазиквотером, как будто я строю типизированные выражения, а не определяю параметры в quoted
, Ничто из очевидного, о чем я мог подумать, не сработало (добавив = _
, явно набрав квазицитату как ValDef
, так далее.).
Я знаю, что могу создать определения параметров вручную:
val valDefs = pss.map(
_.map {
case (n, t) => ValDef(Modifiers(Flag.PARAM), n, TypeTree(t), EmptyTree)
}
)
А теперь baz
Версия (с одним списком параметров) работает:
q"def baz(..${valDefs.head}) = ???"
Но не foo
версия (та, с несколькими списками параметров).
Итак, здесь есть два вопроса. Во-первых, как я могу использовать квази-кавычки, чтобы превратить пару типа имя в параметр ValDef
вне контекста списка параметров в кавычках? И во-вторых, как я могу превратить список списков определений параметров в несколько списков параметров?
Достаточно легко вернуться к ручному построению AST для всей этой чёртовой штуки (см. Мой пост для примера), но я бы хотел вместо этого использовать квазицитаты.
1 ответ
Вот быстрое решение вашей проблемы:
val pss = List(
List(newTermName("x") -> typeOf[Int], newTermName("y") -> typeOf[Char]),
List(newTermName("z") -> typeOf[String])
)
val vparamss: List[List[ValDef]] = pss.map { _.map { case (name, tpe) => q"val $name: $tpe" } }
q"def foo(...$vparamss)"
Как видите, специальный...$splice позволяет определить функцию с несколькими списками аргументов. Сами аргументы функции представлены так же, как и обычные значения.
Другой пример, где... $ это может быть полезно:
val xy = List(List(q"x"), List(q"y"))
q"f(...$xy)" // same as q"f(x)(y)"