次のマクロがあるとします。
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)
}
これは、「実際の」可変引数で期待どおりに機能します。
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): _*)
ただし、これはハックのように感じます。これは、1 つの検証 (引数がリテラルであることの確認) と別の検証 (実際に可変引数があることの確認) を混同しています。引数をリテラルにする必要がない場合 (またはその型をジェネリックにしたい場合) も想像できます。
私は次のことができることを知っています:
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: _*)
最初の例でコンパイル時にエラーが発生することを確認できる最も簡単な方法は何ですか?