ブログから転載。
先ほどこんなTweetを見た。
Elixir v1.3 will have new accessors for nested data structures. Here is how to upcase all languages names in a map: pic.twitter.com/FNTTqjpDeP
— Elixir Lang (@elixirlang) 2016年5月26日
%{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/2、Access.key!/1、Access.elem/1が追加されている。追加されたコミットは464d3ddで、10689f4でAccess.fieldがAccess.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_in、update_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"}]}
]}