再帰処理を毎回書かずにJSON項目を抜く

（この記事は [Elixir (その2)とPhoenix Advent Calendar 2016](http://qiita.com/advent-calendar/2016/elixir2_and_phoenix) 16日目の記事です）

[前回](http://qiita.com/piacere/items/19cb9e7c928edd5f2dc1)、カスタマーサポート向けのツールを作ったりしましたが、その後、「日常業務がプログラミングで無い方向けの、プログラミング教室」的なものを、オフィスのイベントスペースで開催しました

それで、普通のプログラマでも、理解が比較的難しい、「再帰処理によるリストの巡回」をいかにカンタンにできるか、というテーマをクリアするための、小さな関数を作ってみたので、そちらの共有です :sunrise_over_mountains:

ただ、もっとイケてる造りができるかも知れず、「こんなエレガントな書き方あるよ」みたいなご意見あれば、ぜひコメントくださいませ :headphones:

# JSONからtitle項目の値を取りたいけど．．．

Elixirでは、しょっちゅう再帰処理が出てきます

たとえば、[「JSON中にある、全てのtitle項目の値を取る」](https://www.slideshare.net/piacere_ex/elixir1json-75571537)といったような処理がある場合、まぁ普通に再帰で処理しますよね

以下のコードの、title_list()と_title_list()のところですね :kissing:

```ex
defmodule Crawl do
	def get( query \\ "Elixir" ) do
		url( query )
		|> HTTPoison.get!
		|> body
		|> Poison.decode!
		|> title_list
	end

	def title_list( body ), do: _title_list( body, [] )
	defp _title_list( [ %{ "title" => json_title } | tail ], titles ) do
		_title_list( tail, [ json_title | titles ] )
	end
	defp _title_list( [], titles ), do: titles

	def url( query ), do: "https://qiita.com/api/v2/items?query=#{query}"
	def body( %{ status_code: 200, body: json_body } ), do: json_body
end
```

プログラマ向けであっても、このリスト再帰処理を説明した際、スッと入る方もいる一方、かなり細かく動きを追わないとピンと来ない方も多くいました

iexで、以下のような追い方をすれば、比較的、理解が進むとは思いますが、それでも空気を吸うように再帰処理を書けるようになるまで（≒脳の構造を変える、常識が変わる）には、なかなか苦戦するようです

```ex
iex> [ head | tail ] = [ "abc", 123, true ]
["abc", 123, true]
iex> head
"abc"
iex> tail
[123, true]
iex> [ head2 | tail2 ] = tail
[123, true]
iex> head2
123
iex> tail2
[true]
iex> [ head3 | tail3 ] = tail2
[true]
iex> head3
true
iex> tail3
[]
```

慣れたとしても、対象項目や項目数だけが異なる、ソックリさんの関数をちょいちょいコピペする場面は避けたい、と思いますよね？ :muscle_tone1:

# 再帰処理を書かずにJSON項目を抜く関数を作る

Enumモジュールの各関数を普段使っていると、行列操作がとてもエレガントに表現できることに驚きます

なので、この課題も、Enumモジュールの関数のようなノリで解決できないか、と考え始めたのが、きっかけでした

で、色々と考えた結果、以下のような関数を作ってみました

```ex
defmodule MapList do
	def select( list, key1, key2 \\ "", key3 \\ "", key4 \\ "" ), do: _select( list, [], key1, key2, key3, key4 )
	defp _select( [], values, _key1, _key2, _key3, _key4 ), do: Enum.reverse( values )
	defp _select( map_list, values,  key1,  key2,  key3,  key4 ) do
		[ head | tail ] = map_list
		value = cond do
			key2 == "" -> 
				%{ ^key1 => value1 } = head
				value1
			key3 == "" ->
				%{ ^key1 => value1, ^key2 => value2 } = head
				{ value1, value2 }
			key4 == "" ->
				%{ ^key1 => value1, ^key2 => value2, ^key3 => value3 } = head
				{ value1, value2, value3 }
			true       ->
				%{ ^key1 => value1, ^key2 => value2, ^key3 => value3, ^key4 => value4 } = head
				{ value1, value2, value3, value4 }
		end
		_select( tail, [ value | values ], key1, key2, key3, key4 )
	end
　…（その他の関数もあるけど今回は割愛）…
end
```

key1は指定必須ですが、key2～key4は、デフォルト引数でそれぞれ省略可能となっており、指定パターンに応じて、JSON項目から幾つ抜くかの挙動が変わります

各パターンを関数で書き分けることも可能なんですが、再帰呼び出しを毎回書くのがメンドかったので、cond doで条件分岐させちゃいました :sweat_smile:

（その結果、第1引数に[]を受け取る関数を先に定義しないと、コンパイル時にエラーが出ます）

では、この関数を使って、JSONからtitle項目を抜く処理を書き換えてみましょう

```ex
defmodule Crawl do
	def get( query \\ "Elixir" ) do
		url( query )
		|> HTTPoison.get!
		|> body
		|> Poison.decode!
		|> MapList.select( "title" )
	end
　…
```

だいぶ直感的になりましたね

再帰処理が、SQLに化けた感じでしょうか？ :relaxed:

対象項目は、複数指定できるので、たとえばtitleとbodyを両方抜くときは、こんな感じです（4つまで指定可能）

```ex
defmodule Crawl do
	def get( query \\ "Elixir" ) do
		url( query )
		|> HTTPoison.get!
		|> body
		|> Poison.decode!
		|> MapList.select( "title", "body" )
	end
　…
```

もっとも、Enum.map()等に慣れたら、上記より少し複雑だけど、ほぼ似たような書き方ができるようになるので、関数は不要になりますが、これはまた[次回](https://qiita.com/piacere/items/47d8a85de278a55446a8)に :wink: