LoginSignup
3
2

More than 5 years have passed since last update.

マクロを試してみよう

Last updated at Posted at 2016-07-26

はじめに

マクロの理解を深めたいと思い、Scala Documentationのマクロのところを読んでいます。

前者でユースケースが紹介されていて、後者でマクロにまつわるトピックがいくつか紹介されています。

今回はマクロにまつわるトピックを理解するために必要そうな基礎的な内容を確認したいと思います。

確認したい内容は下記になります。

しかし、ときとして def マクロは「ただのメソッド呼び出し」という概念を超越することがある。
例えば、展開されたマクロは、元のマクロの戻り値の型よりも特化された型を持つ式を返すことが可能だ。
Scala 2.10 では、StackOverflow の Static return type of Scala macros で解説したように、
そのような展開は特化された型を保持し続けることができる。

一読しただけでは具体的にどういうことが可能なのか分かりにくいのですが、リンク先にサンプルコードと詳しい解説があります。

これを愚直に確認します。

翻訳は少し省略しましたが、丁寧に翻訳しました。

また、誤解がないように慎重に確認しました。

間違いがありましたら、ご指摘いただけると幸いです。

サンプルコード

追記: 動作確認用のリポジトリを用意しました。

戻り値の型がFooのケース

まず、サンプルコードを確認します。

sbtを使うので、build.sbtを用意します。

build.sbt
lazy val baseSettings = Seq(
  version := "0.0.1",
  scalaVersion := "2.11.8",
  scalacOptions ++= Seq("-deprecation", "-feature", "-unchecked", "-Xlint")
)
lazy val whitebox_macro_sample_foo = (project in file("whitebox_macro_sample_foo")).
  settings(
    baseSettings,
    libraryDependencies += "org.scala-lang" % "scala-compiler" % scalaVersion.value,
    name := "whitebox_macro_sample_foo"
  )
lazy val whitebox_macro_sample_int = (project in file("whitebox_macro_sample_int")).
  settings(
    baseSettings,
    libraryDependencies += "org.scala-lang" % "scala-compiler" % scalaVersion.value,
    name := "whitebox_macro_sample_int"
  )
lazy val whitebox_macro_sample_any = (project in file("whitebox_macro_sample_any")).
  settings(
    baseSettings,
    libraryDependencies += "org.scala-lang" % "scala-compiler" % scalaVersion.value,
    name := "whitebox_macro_sample_any"
  )
lazy val main = (project in file("main")).
  dependsOn(whitebox_macro_sample_foo, whitebox_macro_sample_int, whitebox_macro_sample_any).
  settings(
    baseSettings,
    name := "main"
  )

下記がサンプルコードです:

whitebox_macro_sample_foo/src/main/scala/sample_foo.scala
package sample_foo

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

class Foo
class Bar extends Foo { def launchMissiles = "launching" }

object FooExample {
  def foo: Foo = macro foo_impl
  def foo_impl(c: Context): c.Expr[Foo] =
    c.Expr[Foo](c.universe.reify(new Bar).tree)
}

元のサンプルコードで警告が出たので少し変更しています。whiteboxを追加しています。

REPLでFooExample.fooBar型の値を返すことが確認できます。

また、sbt runするためのメインクラスは下記になります:

main/src/main/scala/Main.scala
object Main extends App {
  import sample_foo._
  FooExample.foo
}

ちなみに、sbt runを実行する場合はマクロを先にコンパイルしておく必要があります。

ここでは明示的に指定した戻り値の型はFooでしたが、Fooを継承したBarが戻り値の型になることを確認しました。

つまり、明示的に指定した戻り値の型よりも特化された型の値が返ることが確認できました。

戻り値の型がIntのケース

次に、Fooの部分をIntに変更してみます。

whitebox_macro_sample_int/src/main/scala/sample_int.scala
package sample_int

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

class Foo
class Bar extends Foo { def launchMissiles = "launching" }

object FooExample {
  def foo: Int = macro foo_impl
  def foo_impl(c: Context): c.Expr[Int] =
    c.Expr[Int](c.universe.reify(new Bar).tree)
}

sbt runするためのメインクラスは下記になります:

main/src/main/scala/Main.scala
object Main extends App {
  import sample_int._
  FooExample.foo
}

マクロのコンパイルはエラーになりませんが、FooExample.fooを呼び出すメインクラスのコンパイルはエラーになります。

さすがに継承関係のない型ではコンパイルエラーになります。

戻り値の型がAnyのケース

もう一つ、今度はFooの部分をAnyに変更してみます。

whitebox_macro_sample_any/src/main/scala/sample_any.scala
package sample_any

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

class Foo
class Bar extends Foo { def launchMissiles = "launching" }

object FooExample {
  def foo: Any = macro foo_impl
  def foo_impl(c: Context): c.Expr[Any] =
    c.Expr[Any](c.universe.reify(new Bar).tree)
}

こちらは、REPLでFooExample.fooBar型の値を返すことが確認できます。

sbt runするためのメインクラスは下記になります:

main/src/main/scala/Main.scala
object Main extends App {
  import sample_any._
  FooExample.foo
}

ここでは明示的に指定した戻り値の型はAnyでしたが、Scalaでは全てのクラスはAnyを継承しているので、Anyを継承したBarが戻り値の型になることを確認しました。

つまり、明示的に指定した戻り値の型よりも特化された型の値が返ることが確認できました。

質問と回答について

質問者が2つ質問していて、1つは「fooメソッドの戻り値の型は何のためか」という質問で、もう1つは「この振る舞いはどこかに仕様化されているか」という質問です。

回答者は「この振る舞いは仕様化されてはいないけれど意図したものです、けれど紛らわしいかもしれません。マクロのシグネチャの戻り値の型のルールを仕上げることを計画しているけれど、今のところはフレキシブルさが欲しいしフレキシブルさがあることは良いことだ」と回答し、「この振る舞いには一貫性がありません。たとえば、型推論の途中でマクロが捉えられたとき、実際の展開の型ではなくその静的なシグネチャが使われます。これは型推論が完了するまで、マクロ展開を意図的に遅延させているからです」と回答しています。

つまり、型推論の途中ではfooメソッドの戻り値の型はFooになるということだと思います。

また、「implicitマクロの戻り値の型がジェネリックや要求されるimplicit valueの型から型推論される必要があるとき、悪いことが起こる。これはtype tagsを生成することに、現在、マクロを使えなくさせている」と述べています。

まとめ

「展開されたマクロは、元のマクロの戻り値の型よりも特化された型を持つ式を返すことが可能だ。」ということを確認しました。

学習の効率を考えると古いマクロやwhiteboxマクロのことは忘れて、blackboxマクロやマクロアノテーションに進むのもいいのかもしれません。

一つ一つ紹介されているトピックを理解しようとすると、whiteboxマクロが使われているので無視できません。

なかなか判断が難しいところですが、今回はwhiteboxマクロの例を確認しました。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2