get_in/2 と Access.all/0 の処理を追っていく
昨日はAccess
モジュールに入った便利関数についてのエントリを書きました。
今日はそれがどうやって実現されているのか読みます。今の時点での最新版はd24a263です。
Access.all/0
まずそもそもAccess.all()
は何を返すのでしょうか。
def all() do &all/3 end
Access.all()
は関数を返すようです。ではAccess.all/3
はどんな関数なのでしょう。
defp all(:get, data, next) when is_list(data) do Enum.map(data, next) end defp all(:get_and_update, data, next) when is_list(data) do all(data, next, [], []) end defp all(_op, data, _next) do raise "Access.all/0 expected a list, got: #{inspect data}" end defp all([head | rest], next, gets, updates) do case next.(head) do {get, update} -> all(rest, next, [get | gets], [update | updates]) :pop -> all(rest, next, [head | gets], updates) end end defp all([], _next, gets, updates) do {:lists.reverse(gets), :lists.reverse(updates)} end
よくわかんないのでKernel
モジュールのget_in/2
を見てみましょう。
get_in/2
def get_in(data, [h]) when is_function(h), do: h.(:get, data, &(&1)) def get_in(data, [h | t]) when is_function(h), do: h.(:get, data, &get_in(&1, t)) def get_in(nil, [_]), do: nil def get_in(nil, [_ | t]), do: get_in(nil, t) def get_in(data, [h]), do: Access.get(data, h) def get_in(data, [h | t]), do: get_in(Access.get(data, h), t)
よくわかんないので実際にget_in/2
を使ったときにどう処理が進むか見てみましょう。
例
map = %{users: [%{name: "john"}, %{name: "mary"}]} get_in(map, [:users, Access.all, :name])
を考えてみましょう。最終的な戻り値は["john", "mary"]
になるはずです。
まずは
def get_in(data, [h | t]), do: get_in(Access.get(data, h), t)
なので、
get_in(Access.get(map, :users), [Access.all, :name])
です。Access.get/2
は第2引数をキーとして第1引数からいい感じにデータを取得します。雑に言うとDict.get/2
みたいなもんです。ということは、
get_in([%{name: "john"}, %{name: "mary"}], [Access.all, :name])
こう。第2引数の先頭がAccess.all
なので、↓の関数にマッチします。
def get_in(data, [h | t]) when is_function(h), do: h.(:get, data, &get_in(&1, t))
一瞬なにやってんだ と思いましたが落ち着きましょう。これはつまりこういうこと。
all(:get, [%{name: "john"}, %{name: "mary"}], &get_in(&1, [:name]))
all/3
もいくつかパターンがあるのは既に見ました。マッチするのはこれ。
defp all(:get, data, next) when is_list(data) do Enum.map(data, next) end
よって
Enum.map([%{name: "john"}, %{name: "mary"}], &get_in(&1, [:name]))
これは簡単。map
してるだけ。以下と等価。
[get_in(%{name: "john"}, [:name]), get_in(%{name: "mary"}, [:name])]
つまり結果は
["john", "mary"]
なんとか答えにたどり着きました。ふう、疲れた、寝たい、のですが1ヶ所気になったコードがあったのでそこだけ最後に。
def get_in(nil, [_]), do: nil def get_in(nil, [_ | t]), do: get_in(nil, t)
これ。get_in(nil, [_ | t])
がget_in(nil, t)
を返しています。つまりnil
から何を取ろうとしてもnil
ということ。
get_in(nil, [:a, :b, :c, :d]) #=> nil
get_in
している途中でnil
になってしまったら、戻り値は必ずnil
ということ。
get_in(%{e: 1}, [:a, :b, :c, :d]) #=> nil
これは覚えておいたほうがいいかもですね。