例から始めましょう。List.fill
これは、Scala2.10のマクロとしてのタプルに相当します。
import scala.language.experimental.macros
import scala.reflect.macros.Context
object TupleExample {
def fill[A](arity: Int)(a: A): Product = macro fill_impl[A]
def fill_impl[A](c: Context)(arity: c.Expr[Int])(a: c.Expr[A]) = {
import c.universe._
arity.tree match {
case Literal(Constant(n: Int)) if n < 23 => c.Expr(
Apply(
Select(Ident("Tuple" + n.toString), "apply"),
List.fill(n)(a.tree)
)
)
case _ => c.abort(
c.enclosingPosition,
"Desired arity must be a compile-time constant less than 23!"
)
}
}
}
この方法は次のように使用できます。
scala> TupleExample.fill(3)("hello")
res0: (String, String, String) = (hello,hello,hello)
この男はいくつかの点で奇妙な鳥です。まず、arity
引数はコンパイル時に使用する必要があるため、リテラル整数である必要があります。Scalaの以前のバージョンでは、(私が知る限り)メソッドの引数の1つがコンパイル時のリテラルであるかどうかを判断する方法さえありませんでした。
次に、Product
リターンタイプは嘘です。静的リターンタイプには、上記のように、引数によって決定された特定のアリティと要素タイプが含まれます。
では、このことをどのように文書化するのでしょうか?現時点ではScaladocのサポートは期待していませんが、マクロメソッドを実行するための規則やベストプラクティス(コンパイル時のエラーメッセージが明確であることを確認するだけでなく)を理解したいと思います。潜在的に奇妙な要求—Scala2.10ライブラリのユーザーにとってはそれほど驚くことではありません。
新しいマクロシステムの最も成熟したデモンストレーション(たとえば、ScalaMock、Slick 、ここにリストされているその他)は、メソッドレベルではまだ比較的文書化されていません。同様のマクロシステムを備えた他の言語のものを含め、任意の例またはポインタをいただければ幸いです。