mmag

ハマったことメモなど

Elixir 1.3に入るAccessモジュールの関数

先ほど、こんなTweetを見た。

%{languages: [%{name: "elixir", type: "functional"}, %{name: "c", type: "procedural"}]}
|> update_in([:languages, Access.all(), :name], &String.upcase/1)

こうすると、

%{languages: [%{name: "ELIXIR", type: "functiocal"}, %{name: "C", type: "procedural"}]}

というmapが得られる。Tweetの例にはname: "john"がいるけど紛らわしいので消した。

見慣れないAccess.all/0が使われていて、:languagesキーに対応するリストの全要素にアクセスしている。

今日の時点では他にもAccess.key/2Access.key!/1Access.elem/1が追加されている。追加されたコミットは464d3ddで、10689f4Access.fieldAccess.keyに変更されている模様。

Access.key/2は、これまで

update_in(map, [:name], &String.upcase/1)

と書いていたものを

update_in(map, [Access.key(:name, "john")], &String.upcase/1)

と書くことで、map:nameキーが無かった場合のデフォルト値を指定できる。

Access.key!/1は、キーが存在しないときに即座にraiseする。

update_in(%{color: "red"}, [Access.key!(:name)], &String.upcase/1)
** (KeyError) key :name not found in: %{color: "red"}

Access.elem/1は、

get_in({:ok, 3}, [Access.elem(1)])  #=> 3

のように、タプルにindexでアクセスできる。

感想

get_inupdate_inは確かに便利なんだろうなと思っていたけれどイマイチ活用できた記憶がない。今回の変更でかなり柔軟になったので、活躍の場が増えるのでは。

例えば以下のデータがあったとする。

users = %{users: [
  %User{name: "John", posts: [%Post{title: "foo", tags: ["elixir", "Ecto"]}]},
  %User{name: "Mary", posts: [%Post{title: "bar", tags: ["BEAM"]}, %Post{title: "baz"}]},
]}

タグは全て小文字にしておきたいな〜と思ったら次のようにすればOK。

update_in(users, [:users, Access.all(), :posts, Access.all(), Access.key(:tags, []), Access.all], &String.downcase/1)

戻り値は

%{users: [
  %User{name: "John", posts: [%Post{tags: ["elixir", "ecto"], title: "foo"}]},
  %User{name: "Mary", posts: [%Post{tags: ["beam"], title: "bar"}, %Post{tags: [], title: "baz"}]}
]}