Предположим, у меня есть такой макрос:
import language.experimental.macros
import scala.reflect.macros.Context
object FooExample {
def foo[A](xs: A*): Int = macro foo_impl[A]
def foo_impl[A](c: Context)(xs: c.Expr[A]*) = c.literal(xs.size)
}
Это работает, как и ожидалось, с "настоящими" varargs:
scala> FooExample.foo(1, 2, 3)
res0: Int = 3
Но поведение с последовательностью, приписываемой типу varargs, меня сбивает (в Scala 2.10.0-RC3):
scala> FooExample.foo(List(1, 2, 3): _*)
res1: Int = 1
И чтобы показать, что с предполагаемым типом не происходит ничего подозрительного:
scala> FooExample.foo[Int](List(1, 2, 3): _*)
res2: Int = 1
Я ожидал здесь ошибки времени компиляции, и это то, что я хочу. В большинстве написанных мной макросов я использовал следующий подход:
object BarExample {
def bar(xs: Int*): Int = macro bar_impl
def bar_impl(c: Context)(xs: c.Expr[Int]*) = {
import c.universe._
c.literal(
xs.map(_.tree).headOption map {
case Literal(Constant(x: Int)) => x
case _ => c.abort(c.enclosingPosition, "bar wants literal arguments!")
} getOrElse c.abort(c.enclosingPosition, "bar wants arguments!")
)
}
}
И это устраняет проблему во время компиляции:
scala> BarExample.bar(3, 2, 1)
res3: Int = 3
scala> BarExample.bar(List(3, 2, 1): _*)
<console>:8: error: bar wants literal arguments!
BarExample.bar(List(3, 2, 1): _*)
Однако для меня это похоже на хакерство - это смешивание одного бита проверки (проверка того, что аргументы являются литералами) с другим (подтверждая, что у нас действительно есть varargs). Я также могу представить случаи, когда мне не нужно, чтобы аргументы были литералами (или когда я хочу, чтобы их тип был универсальным).
Я знаю, что могу сделать следующее:
object BazExample {
def baz[A](xs: A*): Int = macro baz_impl[A]
def baz_impl[A](c: Context)(xs: c.Expr[A]*) = {
import c.universe._
xs.toList.map(_.tree) match {
case Typed(_, Ident(tpnme.WILDCARD_STAR)) :: Nil =>
c.abort(c.enclosingPosition, "baz wants real varargs!")
case _ => c.literal(xs.size)
}
}
}
Но это уродливый способ обработки очень простой (и я полагаю, широко необходимой) части проверки аргумента. Есть ли уловка, которую я здесь упускаю? Как проще всего убедиться, что foo(1 :: Nil: _*)
в моем первом примере выдает ошибку времени компиляции?
xs.head
на самом деле вовсе неc.Expr[A]
- это больше похоже наc.Expr[Seq[A]]
. Вот несколько примеров. - person Travis Brown   schedule 03.12.2012