ElixirのAccess behaviourを完全に理解した

## TIL

- [`Access`] behaviourは、[`Keyword`]と[`Map`]に対応できるが、structには対応できない。
- Elixirのstructsが[`Access`] behaviourを実装しないのにはわけがある。
- 事前にアトムでキーが定義された[`Map`]やstructは、ドット記法（例、`data.key`）でのアクセスが推奨されている。

## Access behaviour

> The Access module defines a behaviour for dynamically accessing keys of any type in a data structure via the `data[key]` syntax.

> Accessモジュールは、`data[key]`構文を介してデータ構造内の任意のタイプのキーに動的にアクセスするための動作を定義します。

(翻訳 by Google)

[`Keyword`]と[`Map`]は似てはいますが、全く異なるものです。Accessモジュールの提供する`data[key]構文`は、[`Keyword`]と[`Map`]の両方に対応していて便利です。
[Good and Bad Elixir by Chris Keathley](https://keathley.io/blog/good-and-bad-elixir.html)では、後々のリファクタリングの容易性を考慮して、[`Map.get/2`]や[`Keyword.get/2`]より[`Access`]を使用したようが良いのではと提唱しています。

## [`Access`]をつかってみる

[`Keyword`]と[`Map`]の差異を気にせずに値を取得できます。便利！

```elixir
k = [abc: [xyz: 123]]
m = %{abc: %{xyz: 123}}

Keyword.get(k, :abc)
Map.get(m, :abc)

k[:abc][:xyz]
m[:abc][:xyz]

k[:abc][:bad][:xyz] # nil
m[:abc][:bad][:xyz] # nil

get_in(k, [:abc, :xyz])
get_in(m, [:abc, :xyz])
```

しかしながらstructは別です。structに対して[`Access`]を使用することはできません。

```elixir
defmodule MyStruct do
  defstruct [:abc]
end

s = %MyStruct{abc: %{xyz: 123}}

s[:abc]
** (UndefinedFunctionError) function MyStruct.fetch/2 is undefined (MyStruct does not implement the Access behaviour)
    MyStruct.fetch(%MyStruct{abc: %{xyz: 123}}, :abc)
    (elixir 1.13.0-rc.0) lib/access.ex:285: Access.get/3

get_in(s, [:abc])
** (UndefinedFunctionError) function MyStruct.fetch/2 is undefined (MyStruct does not implement the Access behaviour)
    MyStruct.fetch(%MyStruct{abc: %{xyz: 123}}, :abc)
    (elixir 1.13.0-rc.0) lib/access.ex:285: Access.get/3

# Kernel.get_in/2だけは裏技があります。
get_in(s, [Access.key(:abc), Access.key(:xyz)])
```

## なぜElixirのstructが[`Access`] behaviourを実装しないのか

[Access]のドキュメントを見てみると、どうも意図的にそうしているようです。

> ... since structs are maps and structs have predefined keys, they only allow the struct.key syntax and they do not allow the `struct[key]` access syntax.

> ... 構造体はマップであり、構造体には事前定義されたキーがあるため、構造体は`struct.key`構文のみを許可し、`struct[key]`アクセス構文は許可しません。

(翻訳 by Google)

よく考えてみたら、納得できました。structを定義する時点でキーを明示しているので、動的でキーの存在を気にしない[`Access`] behaviourよりキーの存在を保証してくれる`struct.key`構文の方が良い気がします。

## どうしてもstructに[`Access`] behaviourを実装したい場合

個人的には、Elixirコアチームの考えに共鳴するので、structは`struct.key`構文のみでアクセスするようにしようと考えています。

万一どうしてもstructに[`Access`] behaviourを実装したい場合、自分で実装してもよいのですが、便利なElixirパッケージがあるので、車輪の再発明をする必要はありません。

- https://github.com/codedge-llc/accessible
- https://github.com/mbramson/struct_access

```elixir
iex

Mix.install([:accessible, :struct_access])

# Using accessible
defmodule MyAccesibleStruct1 do
  defstruct [:abc]
  use Accessible
end

# Using struct_access
defmodule MyAccesibleStruct2 do
  defstruct [:abc]
  use StructAccess
end

# Problem solved!!!
accesible = %MyAccesibleStruct1{abc: %{xyz: 123}}
accesible[:abc][:xyz]
get_in(accesible, [:abc, :xyz])
```

:tada::tada::tada:

[Access]: https://hexdocs.pm/elixir/Access.html
[`Access`]: https://hexdocs.pm/elixir/Access.html
[`Keyword`]: https://hexdocs.pm/elixir/Keyword.html
[`Map`]: https://hexdocs.pm/elixir/Map.html
[`Keyword.get/2`]: https://hexdocs.pm/elixir/Keyword.html#get/3
[`Map.get/2`]: https://hexdocs.pm/elixir/Map.html#get/3