TIL
-
Accessbehaviourは、KeywordとMapに対応できるが、structには対応できない。 - Elixirのstructsが
Accessbehaviourを実装しないのにはわけがある。 - 事前にアトムでキーが定義された
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])
![]()
![]()
![]()