Quasiquotes for multiple parameters and parameter lists
Asked Answered
P

1

9

Quasiquotes are amazing—they make writing macros in Scala hugely less painful, and in my experience they almost always just work exactly as I'd expect. And best of all, they're now available as a plugin in Scala 2.10.

This question is about a small problem that I ran into while writing this blog post. It's on my list of stuff to look into when I can find a few minutes, but I figured I'd post it here in case someone else can beat me to it, and to help other people who are running into the same question.

Suppose I have a list of lists of name-type pairs:

val pss = List(
  List(newTermName("x") -> typeOf[Int], newTermName("y") -> typeOf[Char]),
  List(newTermName("z") -> typeOf[String])
)

I want to turn these into a tree that looks like this:

def foo(x: Int, y: Char)(z: String) = ???

The following works just fine:

q"def bar(${pss.head.head._1}: ${pss.head.head._2}) = ???"

That is, it builds the following tree:

def bar(x: Int) = ???

Which suggests that I should be able to write something like this:

val quoted = pss.map(_.map { case (n, t) => q"$n: $t" })

q"def foo..${quoted.map(ps => q"($ps)")} = 1"

Or a little more simply, with multiple parameters in a single parameter list:

q"def baz(..${quoted.head}) = ???"

Neither works—I get errors like this:

<console>:28: error: type mismatch;
 found   : List[c.universe.Typed]
 required: List[c.universe.ValDef]
           q"def baz(..${quoted.head}) = ???"
                                ^

Fair enough—I can see how it would look to the quasiquoter like I'm building typed expressions rather than defining parameters in quoted. None of the obvious things I could think to try worked (adding = _, explicitly typing the quasiquote as a ValDef, etc.).

I know that I can build the parameter definitions manually:

val valDefs = pss.map(
  _.map {
    case (n, t) => ValDef(Modifiers(Flag.PARAM), n, TypeTree(t), EmptyTree)
  }
)

And now the baz version (with one parameter list) works:

q"def baz(..${valDefs.head}) = ???"

But not the foo version (the one with multiple parameter lists).

So there are two questions here. First, how can I use quasiquotes to turn a name-type pair into a parameter ValDef outside of the context of a quoted parameter list? And second, how can I turn a list of lists of parameter definitions into multiple parameter lists?

It's easy enough to fall back to manual AST construction for the whole damn thing (see my post for an example), but I'd like to be able to use quasiquotes instead.

Pelican answered 1/9, 2013 at 14:51 Comment(2)
1) I believe q"val $n: $t" will work 2) Use ...$ to splice lists of listsPliam
val works perfectly here—thanks! ...$quoted doesn't, though—I'd guess because the inner lists aren't automatically lifted to parameter lists?Pelican
D
5

Here is the quick solution to your problem:

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)"

As you can see special ...$splice lets you define a function with multiple argument lists. Function arguments themselves are represented in the same manner as regular vals.

Another example where ...$it might be useful:

val xy = List(List(q"x"), List(q"y"))
q"f(...$xy)" // same as q"f(x)(y)"
Devinne answered 5/9, 2013 at 19:8 Comment(1)
Works perfectly, thanks! I didn't think to try putting the list of parameter lists in parentheses, but I can see that it makes sense to use that as the syntax here.Pelican

© 2022 - 2024 — McMap. All rights reserved.