Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to define a macro with variadic parameters, and get a type for each parameter?

The following is an obvious variadic function:

def fun(xs: Any*) = ???

We can define a macro in a similar way:

def funImpl(c: Context)(xs: c.Expr[Any]*) = ???

fun(1,"1",1.0)

But in this case, all the arguments are typed as Any. In fact, the compiler knows the types at compile-time, but hides it from us. Is it possible to get a list of arguments and their types in a macro?

like image 1000
Rogach Avatar asked Jun 30 '13 03:06

Rogach


People also ask

How do I use variadic macros?

To use variadic macros, the ellipsis may be specified as the final formal argument in a macro definition, and the replacement identifier __VA_ARGS__ may be used in the definition to insert the extra arguments. __VA_ARGS__ is replaced by all of the arguments that match the ellipsis, including commas between them.

What is macro in parameters?

Macro parameter values are character sequences of no formal type, and they are comma delimited. There is no way to pass in a comma as part of a parameter value. The number of parameters passed can be less than, greater than, or equal to the number of parameters that the macro value is designed to receive.

How many arguments macro can have?

For portability, you should not have more than 31 parameters for a macro. The parameter list may end with an ellipsis (…).

What is __ Va_args __ C++?

macro expansion possible specifying __VA_ARGS__ The '...' in the parameter list represents the variadic data when the macro is invoked and the __VA_ARGS__ in the expansion represents the variadic data in the expansion of the macro. Variadic data is of the form of 1 or more preprocessor tokens separated by commas.


1 Answers

Sure—for example:

import scala.language.experimental.macros
import scala.reflect.macros.Context

object Demo {
  def at(xs: Any*)(i: Int) = macro at_impl
  def at_impl(c: Context)(xs: c.Expr[Any]*)(i: c.Expr[Int]) = {
    import c.universe._

    // First let's show that we can recover the types:
    println(xs.map(_.actualType))

    i.tree match {
      case Literal(Constant(index: Int)) => xs.lift(index).getOrElse(
        c.abort(c.enclosingPosition, "Invalid index!")
      )
      case _ => c.abort(c.enclosingPosition, "Need a literal index!")
    }
  }
}

And then:

scala> Demo.at(1, 'b, "c", 'd')(1)
List(Int(1), Symbol, String("c"), Char('d'))
res0: Symbol = 'b

scala> Demo.at(1, 'b, "c", 'd')(2)
List(Int(1), Symbol, String("c"), Char('d'))
res1: String = c

Note that the inferred types are precise and correct.

Note also that this won't work if the argument is a sequence with the _* type ascription, of course, and that you'll need to write something like the following if you want to catch this case and provide a useful error message:

def at_impl(c: Context)(xs: c.Expr[Any]*)(i: c.Expr[Int]) = {
  import c.universe._

  xs.toList.map(_.tree) match {
    case Typed(_, Ident(tpnme.WILDCARD_STAR)) :: Nil => 
      c.abort(c.enclosingPosition, "Needs real varargs!")
    case _ =>
      i.tree match {
        case Literal(Constant(index: Int)) => xs.lift(index).getOrElse(
          c.abort(c.enclosingPosition, "Invalid index!")
        )
        case _ => c.abort(c.enclosingPosition, "Need a literal index!")
      }
  }
}

See my question here and bug report here for more discussion.

like image 78
Travis Brown Avatar answered Nov 08 '22 23:11

Travis Brown