# 2022/11/18(金) 「マクロ」 is 何? ## モチベーション ちょいちょい耳にする「マクロ」の正体に近づく。 - 参考: 『Go言語でつくるインタプリタ』 付録 - 参考: 『プログラミングElixir』 第2版 23章 ## memo - 自分でif文を作れる! - あえて評価を止めるのは、評価順序を制御したいから → if文でconsequeceが評価されたら困るやん? ## キーワードっぽいもの - プリプロセッサ - プリプロセッサ(マクロ) - Quote ## Elixirでのマクロの話、ちょーわかりやすい [ElixirConf 2017 - Don't Write Macros But Do Learn How They Work - Jesse Anderson ](https://www.youtube.com/watch?v=Bo48sQDb-hk&ab_channel=ElixirConf) - 『プログラミングElixir』 第2版 23章をやっている感じ。 - スライドもわかりやすい ## 2種類 ### テキスト置換マクロシステム → 「検索置換」 ```c #define TIMES_TWO(x) x * 2 #ifndef MY_COOL_FEATURE ... excluded if "my cool feature" is used ... #endif #if MY_COOL_FEATURE ... included if "my cool feature" is used ... #endif ``` ### 構文マクロシステム → してのコード」 ASTっぽいニュアンス??? ASTをいじるやつ? ## 検索置換マクロ > この種のマクロシステムの例としては、C言語のプリプロセッサがある。 > プリプロセッサ > コンパイル前の、前処理 ```c! #include "stdio.h" #define GREETING "Hello there" int main(int args, char *argv[]) { #ifdef DEBUG printf(GREETING " Debug-Mode!\n"); #else printf(GREETING " Production-Mode!\n"); #endif return 0; } ``` PHPとHTMLのように(?)、別の言語が同居できる」と考えてもいいかもしれない! で、コンパイルされる前に、「マクロ言語」というC言語でない言語も、C言語として置換されるから、うまくいく的な感じっぽい。 ``` #ifdef DEBUG printf(GREETING " Debug-Mode!\n"); #else printf(GREETING " Production-Mode!\n"); #endif ``` ↑↑こいつがプリプロセッサにおyって、コンパイルされる前に (DEBUGが truthy じゃないなら) ```c printf("Hello there" " Procduction-Mode!\n"); ``` 「PHPとHTML」の同居でいうところの`<?php ?>` が C言語とマクロ言語では `#` 的な感じ。 競プロ文脈だと、フィットしている!!! https://shibh308.hatenablog.com/entry/2018/02/19/205716 ```c #define FOR(i,a,b) for(int i=(a);i<(b);++i) #define REP(i,n) for(int i=0;i<(n);++i) #define REPR(i,n) for(int i=n;i>=0;i--) ``` ### 構文マクロ コードをテキストとして扱うのではなく、コードをデータとして扱う。 ```monkey x = { "type": "if", "cond": 別のastを表現したハッシュ, "body": ... } // Monkeyだけで(Monkeyの世界だけで)ノードの要素を替えたりとかできる! Goのちからを借りなくても! if x.type == "if" { x.body = 1 } ``` #### 具体例を > 私たちがMonkeyのソースコードをGoで扱っていたときのように扱えるんだ。 > 「このif式をこの関数に渡す」 > 「この関数呼び出しを取り出して保存する」 > **「このlet文に使われている名前を変更する」** ```monkey let a = 1; // 構文マクロで介入して 変数名を a -> x // let x = a とかしたわけじゃないのに、いける! puts(x) // 1 ``` ## elixirのやつ ```elixier! defmodule Sample do defmacro plus_to_minus(expression) do # `3 + 4` -> [3, 4] args = elem(expression, 2) # quoteしたもの == 評価されていない状態の木を返している # `quote do: 3 - 4`ってことだな quote do # 引き算にしちゃうぞ! unquote(Enum.at(args, 0)) - unquote(Enum.at(args, 1)) end end end ``` ```elixier= iex(10)> defmodule Sample do ...(10)> defmacro plus_to_minus(expression) do ...(10)> # `3 + 4` -> [3, 4] ...(10)> args = elem(expression, 2) ...(10)> ...(10)> ...(10)> # quoteしたもの == 評価されていない状態の木を返している ...(10)> # `quote do: 3 - 4`ってことだな ...(10)> quote do ...(10)> # 引き算にしちゃうぞ! ...(10)> unquote(Enum.at(args, 0)) - unquote(Enum.at(args, 1)) ...(10)> end ...(10)> end ...(10)> ...(10)> end warning: redefining module Sample (current version defined in memory) iex:10 {:module, Sample, <<70, 79, 82, 49, 0, 0, 6, 144, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 209, 0, 0, 0, 21, 13, 69, 108, 105, 120, 105, 114, 46, 83, 97, 109, 112, 108, 101, 8, 95, 95, 105, 110, 102, 111, 95, 95, ...>>, {:plus_to_minus, 1}} iex(11)> require Sample Sample iex(12)> Sample.plus_to_minus 10 + 5 5 iex(13)> Sample.plus_to_minus 3 * 4 ``` ### a+b を a-b にするマクロ ``` iex(1)> defmodule Operators do ...(1)> defmacro a + b do ...(1)> quote do ...(1)> to_string(unquote(a)) <> to_string(unquote(b)) ...(1)> end ...(1)> end ...(1)> end {:module, Operators, <<70, 79, 82, 49, 0, 0, 5, 248, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 188, 0, 0, 0, 19, 16, 69, 108, 105, 120, 105, 114, 46, 79, 112, 101, 114, 97, 116, 111, 114, 115, 8, 95, 95, 105, 110, 102, ...>>, {:+, 2}} iex(2)> 3+4 7 iex(3)> import Kernel, ex exit/1 exports/0 exports/1 iex(3)> import Kernel, except: [+: 2] Kernel iex(4)> 3+4 ** (CompileError) iex:4: undefined function +/2 (there is no such import) (elixir 1.14.2) src/elixir.erl:376: :elixir.quoted_to_erl/4 (elixir 1.14.2) src/elixir.erl:277: :elixir.eval_forms/4 (elixir 1.14.2) lib/module/parallel_checker.ex:107: Module.ParallelChecker.verify/1 (iex 1.14.2) lib/iex/evaluator.ex:329: IEx.Evaluator.eval_and_inspect/3 (iex 1.14.2) lib/iex/evaluator.ex:303: IEx.Evaluator.eval_and_inspect_parsed/3 (iex 1.14.2) lib/iex/evaluator.ex:292: IEx.Evaluator.parse_eval_inspect/3 (iex 1.14.2) lib/iex/evaluator.ex:187: IEx.Evaluator.loop/1 iex(4)> import Op Operators OptionParser iex(4)> import Operators Operators iex(5)> 3 + 4 "34" iex(6)> 123+456 "123456" ```