Scalaマクロへの誘いTweetまとめと補足
Scala 2.11時点でマクロはexperimentalな機能です。 将来のバージョンでは変わる可能性がある点にご注意ください。 将来的には scala.meta というプロジェクトに移るようです。 公開されているスライドを見ると、今のマクロ記述から新しい記述へはこう変わるという点が示されています。
Scalaマクロを会得するには、まずリフレクションを学びましょう。https://t.co/imXShu2JHb
— Okuda (@yoskhdia) March 11, 2016
がとてもよくまとまっていて素敵です。その次はScalaDocumentationのリフレクションの項を読みましょう。https://t.co/JuNRmNNTkI
特に「シンボル、構文木、型」が重要です。https://t.co/gD8a8MYJJr
— Okuda (@yoskhdia) 2016年3月11日
と呟いたように、リフレクションをまず学ぶのが遠そうで実は近道だと思います。 Scalaのリフレクションもまだ改善の余地がある気がするのですが、scala.metaが出るともっと良くなるのでしょうか? Javaのリフレクションとは異なり、Scalaはコンパニオンオブジェクトやトレイトなどコンパイル時に解決されるものがあるため、Javaの感覚で理解しないままClassオブジェクトを使うのは危険らしいです。
ここまで来てようやくマクロのドキュメントです。まずはblackboxとwhiteboxの違いを知りましょう。大抵の場合blackboxで十分ですし、標準化されるマクロにはblackboxのみとする予定らしいです。https://t.co/uO8YALEgIU
— Okuda (@yoskhdia) March 11, 2016
マクロの書き方は、まず「defマクロ」を読みましょう。https://t.co/HChpHNqVoe
— Okuda (@yoskhdia) 2016年3月11日
サンプルコードではscala.reflect.macros.Contextをimportしてますが、ここはscala.reflect.macros.blackboxです。
他にはimplicitマクロや抽出子マクロがあるようですが、defマクロが一番基本だと思います。 implicitマクロはまだ使う機会がありそうかなと思えますが、ライブラリならともかくプロダクションコードでimplicitを多用するのも...という論もあり、implicit + macroの組み合わせはデバッグがしづらくなるデメリットの方が大きいんじゃないかなというところです。
そして、抽象構文木がクラスで表されていることを知ったら、APIリファレンスを見ましょう。Treesをまず押さえるのが良いと思います。各Extractorと付くクラスの説明を見ると、Scalaの構文だと何かが分かります。https://t.co/Xqr8Yxk2Om
— Okuda (@yoskhdia) March 11, 2016
ここまで来れば、Scalaのコードを書いてマクロの中でshowRawしてオブジェクト構造と照らしあわせて読むことができるようになったり、逆にオブジェクト構造からScalaコードを想像したりできるようになります。たぶん。
— Okuda (@yoskhdia) 2016年3月11日
裏ではこうやって抽象構文木が作られてるんだなーということが理解できたら、準クォートを使って楽できるところは楽するようにしましょう。ぶっちゃけASTをオブジェクトそのままで構築するの辛すぎです。https://t.co/xLdpKIInV8
— Okuda (@yoskhdia) March 11, 2016
展開するコードを書く場面では準クォートがあるので良いのですが、受け取ったTreeを分解する過程ではどんなオブジェクトがあるかを知らないと分解のしようがないので、準クォートの前にオブジェクトを学んでおいた方が良いです。 分解の仕方を知っておくと、例えば引数で渡される式を分解しながら、途中に特定のメソッドが呼ばれているかを判定するなどができるようになります。 (こういった使い方をするには、ユースケースを限定しないと複雑度が上がってしまうので難しいところですが...)
おめでとうございます。これで、リフレクションが必要なコードを全部マクロで書きたくなっているはずです。新しい世界の幕開けですね。 pic.twitter.com/pAz0hXOJHt
— Okuda (@yoskhdia) 2016年3月11日
まぁ、実際、experimentalだしバグった時に追うの大変なので、プロダクションコードで使うときは、用法用量を守ってメンテ辛くならないようにしましょう。
— Okuda (@yoskhdia) 2016年3月11日
現場からは以上です。
scala.metaになったら手を入れなきゃいけなくなりそうな雰囲気がありますので、マクロを使いたいならしっかりテストを書いておくことをオススメします。
補足
補足1:Prefix(なにかのオブジェクトのメソッドとしてマクロを使うようにした時、そのオブジェクトを参照するためのモノ)は使いたくなることが度々あるので覚えておきましょう。https://t.co/gzn3ipFxtR
— Okuda (@yoskhdia) March 11, 2016
補足2:マクロを呼び出した今のスコープで参照できるimplicitを取りたい、という場合にはinferImplicitValueを使うことでTreeを取得することができます。https://t.co/wwykrZIeTc
— Okuda (@yoskhdia) 2016年3月11日
補足3:いい感じのサンプルとしては、play-jsonのマクロがほどほどの文量です。これを理解できる頃には、自分で任意の型情報から色々加工できるようになっているでしょう。https://t.co/pfdBchj3kT
— Okuda (@yoskhdia) 2016年3月11日
なお、Play2.5で準クォートを使って、かなりスッキリしたコードになっているので、2.4時代のコードを読んで苦しみを分かちあいましょう。https://t.co/LcYxw6hJFr
— Okuda (@yoskhdia) 2016年3月11日
補足5:マクロを定義するとき、
— Okuda (@yoskhdia) 2016年3月11日
object MacroImpl{def impl(c: Context)}
とするよりも
class MacroImpl(c: Context){def impl}
の方がc.universe._のimportが一回で済みます。
これによって、あちこちでasInstanceOfを書かずに済むので、精神衛生上好ましいです。が、どっちの書き方が推奨なのかは、よく分かりません。
— Okuda (@yoskhdia) 2016年3月11日
以上、補足おわり。
※補足4は間違って抜かしてしまっただけです。
※Twitterのツイートをembedすると連ツイだと見難いですね...。