Elixir 構造体（struct）からモジュール名を取得したい


[Elixir]: https://elixir-lang.org/
[Erlang]: https://www.erlang.org/
[Phoenix]: https://www.phoenixframework.org/
[Nerves]: https://hexdocs.pm/nerves
[Livebook]: https://livebook.dev/
[IEx]: https://elixirschool.com/ja/lessons/basics/basics/#%E5%AF%BE%E8%A9%B1%E3%83%A2%E3%83%BC%E3%83%89
[Time.utc_now/1]: https://hexdocs.pm/elixir/Time.html#utc_now/1
[プロトコル]: https://elixirschool.com/ja/lessons/advanced/protocols
[オブジェクト指向プログラミング]: https://ja.wikipedia.org/wiki/%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E6%8C%87%E5%90%91%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0
[__struct__/0]: https://til.hashrocket.com/posts/kfn3wvdqdp-elixir-struct0
[構造体]: https://elixirschool.com/ja/lessons/basics/modules#%E6%A7%8B%E9%80%A0%E4%BD%93-2
[Kernel.struct!/2]: https://hexdocs.pm/elixir/Kernel.html#struct!/2
[Kernel.SpecialForms.%struct{}]: https://hexdocs.pm/elixir/Kernel.SpecialForms.html#%25/2
[Kernel.defstruct/1]: https://hexdocs.pm/elixir/Kernel.html#defstruct/1
[パターンマッチ]: https://elixirschool.com/ja/lessons/basics/pattern_matching
[Map]: https://hexdocs.pm/elixir/Map.html
[Access]: https://hexdocs.pm/elixir/Access.html

[Elixir]の[構造体]（struct）からモジュール名を取得する方法をまとめます。

簡単そうに見えてしっかり勉強しないとややこしい部分があります。

https://qiita.com/torifukukaiou/items/17d55cf896c24b13350e

## 論よりRUN

早速[IEx]を開きます。

```
iex
```

[Time.utc_now/1]を実行すると現在の時刻が`Time`構造体で帰ってきます。

```elixir
%Time{} = Time.utc_now()
```

## [構造体]からモジュール名を取得する方法

[構造体]からモジュール名を取得する方法はいくつかあります。

- 隠された`:__struct__`キーの値を取得する
- [パターンマッチ]

詳しくは公式ドキュメントの[Kernel.SpecialForms.%struct{}]ページで解説されています。

### `:__struct__`キーの値と取得

```elixir
time = Time.utc_now()
time |> is_struct()
time.__struct__
```

ここで注意が必要なのは[Map]とは異なり、[Access]が実装されていないので`data[key]`シンタックスは使えません。

```elixir
iex> Time.utc_now()[:__struct__]
** (UndefinedFunctionError) function Time.fetch/2 is undefined (Time does not implement the Access behaviour. If you are using get_in/put_in/update_in, you can specify the field to be accessed using Access.key!/1)
    (elixir 1.14.2) Time.fetch(~T[23:52:17.032092], :__struct__)
    (elixir 1.14.2) lib/access.ex:288: Access.get/3
    iex:33: (file)
```

https://qiita.com/mnishiguchi/items/3d894842aebd36f30c84

### `:__struct__`キーに対して[パターンマッチ]

```elixir
%{__struct__: mod} = Time.utc_now()
mod
```

### 構造体名に対して[パターンマッチ]

```elixir
%mod{} = Time.utc_now()
mod
```

この特性は（構造体の名前が不要でも）ある値が構造体であることを確認する目的に利用できます。

```elixir
%_{} = Time.utc_now()
```

## `struct`という名の関数たち

余談ですが、`struct`という名の関数がいくつか存在します。知らないと混乱するかもしれません。

## [`__struct__/1`][Kernel.defstruct/1]

- [Kernel.defstruct/1]により定義される
- [構造体]そのものを返す関数

詳しくは公式ドキュメントの[Kernel.defstruct/1]ページで解説されています。

```elixir
Time.__struct__(
  calendar: Calendar.ISO,
  hour: 0,
  microsecond: {0, 0},
  minute: 0,
  second: 0
)
```

結果は普通に構造体を生成する場合と同じです。

```elixir
%Time{
  calendar: Calendar.ISO,
  hour: 0,
  microsecond: {0, 0},
  minute: 0,
  second: 0
}
```

こういうヘンテコなコードも成り立ってしまいます。

```elixir
Time.utc_now.__struct__.__struct__(
  calendar: Calendar.ISO,
  hour: 0,
  microsecond: {0, 0},
  minute: 0,
  second: 0
)
```

## [Kernel.struct!/2]

- 構造体を作成、更新する関数

```elixir
struct!(Time, %{
  calendar: Calendar.ISO,
  hour: 0,
  microsecond: {0, 0},
  minute: 0,
  second: 0
})
```

```elixir
time = Time.utc_now
struct!(time, %{ microsecond: {0, 0} })
```

## [プロトコル]

通常、[構造体]のモジュール名を取得することはあまりないと思います。`:__struct__`キーは[Elixir]言語にサポートされているので、使いたければ使って問題ないそうです。公式ドキュメントの[Kernel.SpecialForms.%struct{}]のページで解説されています。

頻繁に[構造体]のモジュール名が必要となる場合、データと振る舞いがパッケージ化された[オブジェクト指向プログラミング]的な構造になっている可能性があり、その場合は[プロトコル]を実装するとスッキリする場合あると以前聞いたことがあります。知らんけど。

## ご参考までに

https://qiita.com/piacerex/items/e0b6e46b1325bb931122

https://qiita.com/torifukukaiou/items/1edb3e961acf002478fd

https://qiita.com/piacerex/items/e5590fa287d3c89eeebf