(この記事は「fukuoka.ex Elixir/Phoenix 2021 Advent Calendar」の2日目です)
昨日の記事は @koga1020 さんでした.
fukuoka.ex所属の@hisawayです.自称メタプロ芸人です.
マクロの小ネタになります.
IO.inspect(ビギナー向け)
iexでサクッと動作確認とかしたいときにどうしますか.
IO.inspect
を使うとパイプライン|>
で繋げられるのでとてもスマートに書けます.
iex(1)> func1 = &(&1 + 1)
#Function<44.97283095/1 in :erl_eval.expr/5>
iex(2)> func2 = &(&1 * 2)
#Function<44.97283095/1 in :erl_eval.expr/5>
iex(3)> 1234 |> func1.() |> IO.inspect |> func2.()
1235
2470
inspectの引数
ただパイプラインがずっと続くとどの関数を呼び出した後かわからなくなりますね?
iex(3)> 1234 |> func1.() |> IO.inspect |> func2.()
1235 # <- ぱっと見どこ?
2470
そういう時のためか,inspectにはlabelをつけることができます.
iex(4)> 1234 |> func1.() |> IO.inspect(label: "func1") |> func2.()
func1: 1235
2470
見やすくなりましたね.
ここまでが本題の前提になります.
カスタムinspect
見やすくなったおかげで少し作りやすくなりましたが,毎回書くのは正直面倒です.
Phoenixで出てくるログのようなそれっぽいものがしたい...
ということでElixirマクロを使います.
コピペで使えますのでご安心を.
defmodule MyIO do
defmacro inspect(variable) do
name = case variable do
{{:., _, [{atom, _, _}]}, _, _} -> atom |> Atom.to_string()
{atom, _, _} -> atom |> Atom.to_string()
end
quote do
IO.inspect(unquote(variable), label: unquote(name))
end
end
end
上記ファイルをiexを起動したパスにおいて後は次のようにiexに打ち込みます.
※iexでUNIXコマンド等使えるので打って確認しましょう.
iex> Code.compile_file("MyIO.exs")
[
{MyIO,
<<70, 79, 82, 49, 0, 0, 7, 72, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 178,
0, 0, 0, 19, 11, 69, 108, 105, 120, 105, 114, 46, 77, 121, 73, 79, 8, 95,
95, 105, 110, 102, 111, 95, 95, 10, 97, ...>>}
]
iex> require MyIO
MyIO
あとはMyIO.inspect
に変えておしまいです.
iex> 1234 |> func1.() |> MyIO.inspect |> func2.() |> MyIO.inspect
func1: 1235
func2: 2470
2470
少しだけinspectが便利になりました.
解説(中級者向け)
まずは値確認のためにマクロにinspect
を追加.
defmodule MyIO do
defmacro inspect(variable) do
# 確認用に追加
IO.inspect(variable)
name = case variable do
{{:., _, [{atom, _, _}]}, _, _} -> atom |> IO.inspect(label: "pt2") |> Atom.to_string()
{atom, _, _} -> atom |> Atom.to_string() |> IO.inspect(label: "pt1")
end
quote do
IO.inspect(unquote(variable), label: unquote(name))
end
end
end
あとは呼び出して値を確認します.
マクロはもらった値のASTをcase
でパターンマッチします.
iex> 1234 |> func1.() |> MyIO.inspect |> func2.() |> MyIO.inspect
{{:., [line: 9], [{:func2, [line: 9], nil}]}, [line: 9],
[
{{:., [line: 9],
[
{:__aliases__, [counter: -576460752303423486, line: 9], [:MyIO]},
:inspect
]}, [no_parens: true, line: 9],
[{{:., [line: 9], [{:func1, [line: 9], nil}]}, [line: 9], [1234]}]}
]}
{{:., [line: 9], [{:func1, [line: 9], nil}]}, [line: 9], [1234]}
func1: 1235
func2: 2470
2470
AST上では,一番後ろのパイプライン|>
が先頭に来ます.
そのおかげで再帰を使ってマッチさせる必要がありません.
まとめ
マクロを使ってinspectをちょっと便利にしました.
明日2021/12/03(金)は @torifukukaiou さんです!