読者です 読者をやめる 読者になる 読者になる

tech::hexagram

personal note for technical issue.

任意で渡ってきた変数が特定のStructかどうか判定する方法

is_struct みたいな便利メソッドがないようなので以下のように判定する必要があった。 割と面倒だったのでまとめておく。

例として、任意で受け取った引数 argsUser のStructかどうかを判定する。

StructはMapの拡張になっている

まずStructは Mapを拡張した拡張であるので、 is_map(args) がtrueになる。

Structs are extensions on top of maps that bring default values, compile-time guarantees and polymorphism into Elixir.

ref: Structs - Elixir

iex(1)> user_struct = %User{}
%User{...}
iex(2)> string = "test"
"test"
iex(3)> is_map(user_struct)
true
iex(4)> is_map(string)
false

Structは __struct__ というkeyを保持している

In the example above, pattern matching works because underneath structs are bare maps with a fixed set of fields. As maps, structs store a “special” field named struct that holds the name of the struct:

次に、Structの場合は __struct__ というkeyを保持しているので、「MapであるがStructでない変数」の場合は Map.has_key?(args, :__struct) で弾くことが出来る。

iex(5)> map = %{hoge: "huga"}
%{hoge: "huga"}
iex(6)> map |> Map.has_key?(:__struct__)
false
iex(7)> user_struct |> Map.has_key?(:__struct__)
true

__struct__ にはStructの名前が入っている

args.__struct__User であればtrueになるので、あとはこれを比較すれば良い。

iex(8)> not_user_struct = %NotUser{}
%NotUser{...}

iex(9)> user_struct.__struct__ == User
true
iex(10)> not_user_struct.__struct__ == User
false

まとめると

def is_user?(args) do
  is_map(args) && Map.has_key?(args, :__struct__) && args.__struct__ == User
end