# 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"
```