RubyKaigi Takeout 2021 で Ruby のマクロについて話します
先日 RubyKaigi Takeout 2021 のスピーカーとスケジュールが発表されました。
わたしも 3日目の朝一に Use Macro all the time ~ マクロを使いまくろ ~
というタイトルで登壇させていただきます。
内容はタイトルにも書かれている通りマクロの話をします。
マクロと言ってもいわゆる C言語のようなプリプロセスマクロではなくて『AST レベルで Ruby の構文を別の構文へと変換すること』を Ruby のマクロと定義し、Ruby でどのようにマクロを表現するのか、みたいな話をする予定です。
まあもっとさっくりう言うと『Ruby でマクロを実装してみたらこうなりました』みたいな感じですかねえ。
例えばマクロを使用することで、
hoge.foo.bar
のようなコードを
hoge&.foo&.bar
のようにぼっち演算子呼び出しとして変換する事ができたり、
CONST_HOGE = [1, 2, 3]
というような定数定義しているコードを
CONST_HOGE = [1, 2, 3].freeze
のように暗黙的に freeze
したり、
![a, b, c]
というようなコードを
{ a: a, b: b, c: c }
みたいな Hash
に展開するようなことを実現することができます。
Ruby のマクロは以下のような過程を経て Ruby のコードから別の Ruby のコードへと変換しています。
-
↓ AST に変換
変換前の AST データ
↓ AST の種類などを参照しながら特定の AST を別の AST に変換
変換後の AST データ
開発中のコードをちょっとだけお見せすると Ruby のマクロを使うことによって以下のようなことを実現することができます。
# ※開発中のコードなので当日までに変わるかもね! using Macro::Refine::Source # 特定の構文を変換するマクロを定義する module MyMacro using Macro::Macroable # cat! というメソッド呼び出しを任意のコードに変換する def cat!(num = ast { 1 }) ast { "にゃーん" * $num } end macro_function :cat! # 範囲リテラルを freeze するコードに変換する def freezing(node, parent) ast { $node.freeze } end macro_node :DOT2, :freezing macro_node :DOT3, :freezing # 代入式を freeze するコードに変換する def assign_freezing(node, name:, value:) ast { $name = $value.freeze } end macro_pattern pat { $name = $value }, :assign_freezing end # 変換したい Ruby のコードを Proc で定義 body = proc { use_macro! MyMacro puts cat! puts cat!(3) value = [2, 3, 5, 7, 11, 13, 17].grep((0..10)) HOGE = "にゃーん" + "にゃーん" } # マクロを適用させた AST に変換する result = Macro.compile_of(body) # 変換させた AST から Ruby のソースコードを取得する pp result.source # 以下のようなコードに変換される __END__ # cat!(n) が "にゃーん" * n に変換される puts "にゃーん" * 1 puts "にゃーん" * 3 # (0..10) が (0..10).freeze に変換される # value = hoge が value = hoge.freeze に変換される value = [2, 3, 5, 7, 11, 13, 17].grep((0..10).freeze).freeze HOGE = ("にゃーん" + "にゃーん").freeze
上記のような書き方で Ruby のコードを AST レベルで別のコードへと変換するような実装を書いています。
ちなみに実装はすべて Ruby で行っているので MRI 側で特に何か機能を追加したりとかはしていません。
この登壇でメタプロの向こう側ってやつを見せてやりますよ(誇張。
と、言うことでこういう話が好きな方は当日までお楽しみに!
ちなみに去年を除くとこれが RubyKaigi 初参加になります。
RubyKaigi 初参加で初登壇の実績を解除したぜ…。