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では、後々のリファクタリングの容易性を考慮して、Map.get/2
やKeyword.get/2
よりAccess
を使用したようが良いのではと提唱しています。
Access
をつかってみる
Keyword
とMap
の差異を気にせずに値を取得できます。便利!
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
を使用することはできません。
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パッケージがあるので、車輪の再発明をする必要はありません。
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])