Elixir
Phoenix

Excelから関数型言語マスター2回目:「列の抽出」と「Web表示」

(この記事は、「Elixir or Phoenix Advent Calendar 2017」の8日目です)

昨日は、@takasehideki さんの「ElixirでIoT#1:IoTボードへのElixir環境の構築とEEloTツールキットの紹介」でした。


fukuoka.ex代表のpiacereです
今回もご覧いただいて、ありがとうございます:bow:

この連載の、前回までの記事は、以下になります
 |> Excelから関数型言語マスター1回目:行の「並べ替え」と「絞り込み」

今回は、「列の抽出」を行い、その後、「Web表示」と、Web上での「フィルタ」「並べ替え」まで行きます


:stars::stars::stars::stars::stars: お知らせ :stars::stars::stars::stars::stars:
fukuoka.exコアメンバーのインタビュー 第3回(最終回)を公開しました

過去のインタビューは、以下にあります
インタビュー 第1回
インタビュー 第2回

image.png

複数列のデータ

「列の抽出」の前段として、複数列のデータを扱ってみましょう

image.png

これをElixirで表現すると、以下のような感じになります(列名は、日本語から英語に変えています)

iexを起動して、入力してください

iex> data = [
  %{ "name" => "enぺだーし", "age" => 49, "team" => "有限会社デライトシステムズ", "position" => "代表取締役、性能探求者" }, 
  %{ "name" => "ざっきー", "age" => 45, "team" => "公立大学法人 北九州市立大学", "position" => "准教授、カーネルハッカー" }, 
  %{ "name" => "つちろー", "age" => 34, "team" => "カラビナテクノロジー株式会社", "position" => "リードエンジニア、アプリマイスター" }, 
  %{ "name" => "ゆじかわ", "age" => 30, "team" => "カラビナテクノロジー株式会社", "position" => "リードエンジニア、グロースハッカー" }, 
  %{ "name" => "piacere", "age" => 43, "team" => "株式会社TechJIN", "position" => "CTO、福岡Elixirプログラマ、重力プログラマ、技術顧問" }
]

[~]の囲みは、前回の[ 323, 999, 54 ]と同じ、行の並びを表す「リスト」と呼びます

その中にある、各行の複数列を並べている、%{~}の囲みは、「マップ」と呼びます

上記の複数列データは、この「マップ」を「リスト」で包んでいるので「マップリスト」と呼んだりします

複数列データから「列の抽出」

マップリストから「列の抽出」を行うには、Elixirでは、Enum.map()を使います

これは、Excelで言えば、「非表示」をしているようなイメージです

image.png

Enum.map()は、各リストを順に辿り、fn()の後ろの処理を実施※し、その結果をリストで返します
(※前回のEnum.filter()と同様)

マップの特定列を指定する[ "~" ]を使って、複数行から、名前だけを抽出します

image.png

iex> data |> Enum.map( fn( record ) -> record[ "name" ] end )
["enぺだーし", "ざっきー", "つちろー", "ゆじかわ", "piacere"]

名前のリストが返ってきました

次に、このリストで返ってきているところを、マップリストで返すようにしてみます

「->」から「end」の部分で、%{~}のマップを作れば、マップリストになります

iex> data |> Enum.map( fn( record ) -> %{ "name" => record[ "name" ] } end )
[
  %{"name" => "enぺだーし"},
  %{"name" => "ざっきー"},
  %{"name" => "つちろー"},
  %{"name" => "ゆじかわ"},
  %{"name" => "piacere"}
]

名前に加え、年齢も抽出します

iex> data |> Enum.map( fn( record ) -> %{ "name" => record[ "name" ], "age" => record[ "age" ] } end )
[
  %{"age" => 49, "name" => "enぺだーし"},
  %{"age" => 45, "name" => "ざっきー"},
  %{"age" => 34, "name" => "つちろー"},
  %{"age" => 30, "name" => "ゆじかわ"},
  %{"age" => 43, "name" => "piacere"}
]

このように、Enum.map()を使って、マップリストから「列の抽出」を行うことができます

ちなみにforを使うと…

Elixirにも「for」という構文があり、これを使うと、Enum.map()と同じように「列の抽出」ができます

iex> for record <- data do
...>   %{ "name" => record[ "name" ], "age" => record[ "age" ] }
...> end
[
  %{"age" => 49, "name" => "enぺだーし"},
  %{"age" => 45, "name" => "ざっきー"},
  %{"age" => 34, "name" => "つちろー"},
  %{"age" => 30, "name" => "ゆじかわ"},
  %{"age" => 43, "name" => "piacere"}
]

forの使いどころは、この後出てくる、Web表示のような、出力したら、それ以降で出力結果を使うことが無いケースです(イマイチ、ピンと来なければ、Web表示でのみforを使う、と機械的に覚えておいてください)

forはforでは無い【※プログラミング経験者のみご覧を】

forを最初に出さなかったのは、以下3つの理由からです

 ①既存のオブジェクト指向言語の「for」と同じでは無いから
  →for中で変数代入しても次のループではその代入は消えてしまう
 ②Enum.map()のように、パイプを繋いで使えないから
 ③「馴染みあるせいで使いたくなる」という思考に陥ることが関数型言語に移れない罠だから

恐らくforを最初に出していたら、既存のプログラミング言語を思い出しながら、Elixirを覚えていき、その先には「Enum.map()は覚えなくても良いでは無いか」という、過去のパターンに依存した思考に及ぶ可能性が高いからです

この思考こそが、「関数型言語」という新たなパラダイムに移行することを妨げる大きな原因なのです

Phoenixのインストール、PJ作成、起動

さて、ここまでで習得した、「フィルタ」「並べ替え」を、「Web表示」してみましょう

ElixirのWebアプリケーションフレームワーク「Phoenix」をインストールします

iexをCtrl+cを2回押して抜けてから、下記コマンドを入力します

mix archive.install  https://github.com/phoenixframework/archives/raw/master/phx_new.ez

次に、Phoenixプロジェクトを作成します

mix phx.new sample --no-brunch --no-ecto

Phoenixサーバーを起動します

iex -S mix phx.server

ブラウザで「localhost:4000」にアクセスすると、Phoenixで作られたデフォルトのWebページが見れます

image.png

複数列データのWeb表示

それでは、Phoenixで作ったWebページ上に、複数列データを表示してみましょう

以下のように、デフォルトのWebページを書き換えます

lib/sample_web/templates/page/index.html.eex
<%
data = 
[
  %{ "name" => "enぺだーし", "age" => 49, "team" => "有限会社デライトシステムズ", "position" => "代表取締役、性能探求者" }, 
  %{ "name" => "ざっきー", "age" => 45, "team" => "公立大学法人 北九州市立大学", "position" => "准教授、カーネルハッカー" }, 
  %{ "name" => "つちろー", "age" => 34, "team" => "カラビナテクノロジー株式会社", "position" => "リードエンジニア、アプリマイスター" }, 
  %{ "name" => "ゆじかわ", "age" => 30, "team" => "カラビナテクノロジー株式会社", "position" => "リードエンジニア、グロースハッカー" }, 
  %{ "name" => "piacere", "age" => 43, "team" => "株式会社TechJIN", "position" => "CTO、福岡Elixirプログラマ、重力プログラマ、技術顧問" }
]
%>
<table border="1">
<%= for record <- data do %>
<tr>
  <td><%= record[ "name" ] %></td>
  <td><%= record[ "age" ] %></td>
  <td><%= record[ "team" ] %></td>
  <td><%= record[ "position" ] %></td>
</tr>
<% end %>
</table>

index.html.eex上では、<% ~ %>もしくは<%= ~ %>で囲んだ部分に、Elixirを書くことができます

ここでは、forでデータを1行ずつ取り出すループを行い、その中で[ "~" ]で各列の値を取得しています

その結果、以下のようなWebページが表示されるようになります

image.png

Web上での複数列データの「フィルタ」

Enum.map()と同じ要領で、Enum.filter()を複数列データに対して行えます

dataの後の「|> Enum.filter( fn( record ) -> record[ "age" ] >= 43 end )」が該当部分です

lib/sample_web/templates/page/index.html.eex
<%
data = 
[
  %{ "name" => "enぺだーし", "age" => 49, "team" => "有限会社デライトシステムズ", "position" => "代表取締役、性能探求者" }, 
  %{ "name" => "ざっきー", "age" => 45, "team" => "公立大学法人 北九州市立大学", "position" => "准教授、カーネルハッカー" }, 
  %{ "name" => "つちろー", "age" => 34, "team" => "カラビナテクノロジー株式会社", "position" => "リードエンジニア、アプリマイスター" }, 
  %{ "name" => "ゆじかわ", "age" => 30, "team" => "カラビナテクノロジー株式会社", "position" => "リードエンジニア、グロースハッカー" }, 
  %{ "name" => "piacere", "age" => 43, "team" => "株式会社TechJIN", "position" => "CTO、福岡Elixirプログラマ、重力プログラマ、技術顧問" }
]
|> Enum.filter( fn( record ) -> record[ "age" ] >= 43 end )
%>
<table border="1">
<%= for record <- data do %>
<tr>
  <td><%= record[ "name" ] %></td>
  <td><%= record[ "age" ] %></td>
  <td><%= record[ "team" ] %></td>
  <td><%= record[ "position" ] %></td>
</tr>
<% end %>
</table>

43歳以上のデータに「フィルタ」されました

image.png

Web上での複数列データの「並べ替え」

Enum.sort()を複数列データに対して行うには、「現在行」と「次行」の列同士を比較する必要があるため、fn()に2つの引数を指定します

dataの後の「|> Enum.sort( fn( record_current, record_next ) -> record_current[ "age" ] < record_next[ "age" ] end )」が該当部分です

lib/sample_web/templates/page/index.html.eex
<%
data = 
[
  %{ "name" => "enぺだーし", "age" => 49, "team" => "有限会社デライトシステムズ", "position" => "代表取締役、性能探求者" }, 
  %{ "name" => "ざっきー", "age" => 45, "team" => "公立大学法人 北九州市立大学", "position" => "准教授、カーネルハッカー" }, 
  %{ "name" => "つちろー", "age" => 34, "team" => "カラビナテクノロジー株式会社", "position" => "リードエンジニア、アプリマイスター" }, 
  %{ "name" => "ゆじかわ", "age" => 30, "team" => "カラビナテクノロジー株式会社", "position" => "リードエンジニア、グロースハッカー" }, 
  %{ "name" => "piacere", "age" => 43, "team" => "株式会社TechJIN", "position" => "CTO、福岡Elixirプログラマ、重力プログラマ、技術顧問" }
]
|> Enum.sort( fn( record_current, record_next ) -> record_current[ "age" ] < record_next[ "age" ] end )
%>
<table border="1">
<%= for record <- data do %>
<tr>
  <td><%= record[ "name" ] %></td>
  <td><%= record[ "age" ] %></td>
  <td><%= record[ "team" ] %></td>
  <td><%= record[ "position" ] %></td>
</tr>
<% end %>
</table>

年齢の若い順で「並べ替え」されました

image.png

年齢の高い順で「並べ替え」するには、「record_current[ "age" ] < record_next[ "age" ]」を「record_current[ "age" ] > record_next[ "age" ]」に変えるだけです

image.png

終わり

前回と今回で、ExcelからElixirへの読み替えを行い、Web表示まで辿り着きました

関数型言語によるWebアプリ開発が、思った以上にカンタンじゃ無いか…と感じていただけたら幸いです

なお、Web上での「列の抽出」は、列指定が固定だと使う場面が無いので、少し先に解説することになります

次回は、「WebにDBデータ表示」を行います

明日は、@tuchiro さんの「ElixirでSI開発入門 #2 Ectoで楽観的ロック」です


:shamrock::shamrock::shamrock: お礼:4/20のfukuoka.ex#8、おかげさまで盛り上がりました :shamrock::shamrock::shamrock:

fukuoka.ex#8「2018年 春のElixir入学式」、賑やかにやりました(地味に女子率多し)!

image.png

p.s.「いいね」よろしくお願いします

ページ左上の image.pngimage.png のクリックを、どうぞよろしくお願いします:bow:
ここの数字が増えると、書き手としては「ウケている」という感覚が得られ、連載を更に進化させていくモチベーションになりますので、もっとElixirネタを見たいというあなた、私達と一緒に盛り上げてください!:tada: