Skip to content

Latest commit

 

History

History
125 lines (89 loc) · 4.07 KB

20.3.md

File metadata and controls

125 lines (89 loc) · 4.07 KB

20.3 コードとして表現を利用する

マクロ、quate関数を使って、コードを内部表現に展開すると、コンパイル結果(タプル)に、自動的には追加されなくなる

内部表現を注入する方法は2つ

  • マクロを使う
  • Code.eval_quoted を使う
    「20.6 コード片を走らせる他の方法」参照

マクロを使う場合

  1. マクロに引数の内部表現が渡される
  2. マクロで処理する
    マクロで評価された最後の式を返す
  3. プログラムの内部表現にマクロの戻り値を差し込む
  4. 差し込まれたプログラムの実行結果をマクロの呼び出し元に返す

マクロの呼び出し元には、マクロの戻り値(内部表現)を実行した結果が返される

goh@goh% iex "macros/eg.ex"
Erlang/OTP 21 [erts-10.1.2] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe] [dtrace]

{{:., [line: 10], [{:__aliases__, [line: 10], [:IO]}, :puts]}, [line: 10],
 ["hello"]}
hello

「IO.puts("hello")」の内部表現を実行した結果、すなわち「hello」が呼び出し元に返される

goh@goh% iex "macros/eg1.ex"
Erlang/OTP 21 [erts-10.1.2] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe] [dtrace]

{{:., [line: 10], [{:__aliases__, [line: 10], [:IO]}, :puts]}, [line: 10],
 ["hello"]}
Different code

quote関数で「IO.puts "Different code"」の内部表現が返され、呼び出し元ではそれが実行される

既存のコードをquoteブロックに差し込む方法は2つ

  • unquote関数を使う
  • bindingを使う

unquote関数

Kernel.SpecialForms.unquote(expr)

  • expr: 内部表現

quoteブロックの中でしか使えない unquote よりも inject_code_fragment の方が適切な名前

goh@goh% iex "macros/eg2.ex"

Erlang/OTP 21 [erts-10.1.2] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe] [dtrace]

warning: variable "code" is unused
  macros/eg2.ex:2

warning: variable "code" does not exist and is being expanded to "code()", please use parentheses to remove the ambiguity or change the variable name
  macros/eg2.ex:11

** (CompileError) macros/eg2.ex:11: undefined function code/0
    expanding macro: My.macro/1
    macros/eg2.ex:11: Test (module)

quoteブロック内は、通常のコードとしてパースされる quoteブロック内で、内部表現を処理してほしい場合は unquote を使う必要がある

goh@goh% iex "macros/eg2.ex"
Erlang/OTP 21 [erts-10.1.2] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe] [dtrace]

hello
:ok

quote関数では、ブロック内のコードをパースして内部表現に変換している unquote関数では、関数内のコードをパースせず、そのままコピーする

unquote関数内のコードは、遅延評価されるとも言える quote関数のブロックがパースされるときではなく、内部表現が生成されるときに実行される

"sum=#{1 + 2}" は、「1 + 2」を評価し、結果を文字列に埋め込む quote do: def unquote(name) do end は、nameの中身をquoteが返す内部表現に埋め込む

Listの展開 - unquote_aplicingA

Kernel.SpecialForms.unquote_splicing(expr)

  • expr: リスト

指定されたリストを展開する

> Code.eval_quoted(quote do: [1, 2, unquote([3, 4])])
{[1, 2, [3, 4]], []}
> Code.eval_quoted(quote do: [1, 2, unquote_splicing([3, 4])])
{[1, 2, 3, 4], []}
> Code.eval_quoted(quote do: [?a, ?=, unquote_splicing('1234')])
{'a=1234', []}

myifマクロへ戻る

goh@goh% iex "macros/myif2.exs"
Erlang/OTP 21 [erts-10.1.2] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe] [dtrace]

1 != 2
  1. My.if は条件とキーワードリストを受け取る
  2. キーワードリストから do:else: を取り出す
  3. quoteブロックを開く
    1. unquote を使って条件を注入する
      • false or nil => do:
      • true => else:
    2. unquote を使って do: または else: に対応するコードを実行する