10
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

fukuoka.ex Elixir/PhoenixAdvent Calendar 2021

Day 2

[Elixir] ラベル付きinspectのラベルをマクロで端折る

Last updated at Posted at 2021-12-01

(この記事は「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マクロを使います.
コピペで使えますのでご安心を.

MyIO.exs
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 さんです!

10
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?