【本コラムは、5分で読めて、15分くらいでお試しいただけます】
piacereです、ご覧いただいてありがとございます
前回は、Elixirをインストールし、データの並替えと絞り込みを行いました
今回は、「列の抽出」を行い、その後、Web上での「フィルタ」「並べ替え」まで行きます
本コラムは、さまざまな時期のElixir/Phoenix環境で検証済みです
■「ExcelからElixir入門」シリーズの目次
①データ並替え/絞り込み
|> ②データ列抽出、Web表示
|> ③WebにDBデータ表示
|> ④Webに外部APIデータ表示
|> ⑤Webにグラフ表示
|> ⑥SPAからPhoenix製APIを呼び出す(表示編)【LiveView版】
|> ⑦SPAからPhoenix製APIを呼び出す(更新編)【LiveView版】
|> ⑧Gigalixirに本番リリース
|> ⑨ElixirサーバサイドのみでReactと同じSPA/リアルタイムUIが作れる「LiveView」
|> ⑩ElixirサーバサイドSPAをスマホで見るためにGigalixirリリース
|> ⑪Gigalixir上のLiveViewアプリに独自ドメイン名を付与して正式なアプリ公開
|> ⑫Elixir/PhoenixのCRUD Webアプリをリリース
お礼:4/20のfukuoka.ex#8、おかげさまで盛り上がりました
fukuoka.ex#8「2018年 春のElixir入学式」、賑やかにやりました(地味に女子率多し)!
複数列のデータ
列抽出の前段として、複数列のデータを扱ってみましょう
これをElixirで表現すると、以下のような感じになります(データは、日本語から英語に変えています)
iexを起動して、入力してください
iex> datas = [
%{ "name" => "en-pedasi", "age" => 54, "team" => "Delight Systems", "position" => "CEO" },
%{ "name" => "zacky", "age" => 50, "team" => "Kitakyushu University", "position" => "Associate professor" },
%{ "name" => "takuto", "age" => 40, "team" => "karabiner inc", "position" => "Tech lead" },
%{ "name" => "piacere", "age" => 48, "team" => "DigiDockConsulting", "position" => "CTO" }
]
[xxx]
の囲みは、前回の[ 323, 999, 54 ]と同じ、行の並びを表す「リスト」と呼びます
その中にある、各行の複数列を並べている、%{xxx}
の囲みは、「マップ」と呼びます
上記の複数列データは、この「マップ」を「リスト」で包んでいるので「マップリスト」と呼んだりします
複数列データから列抽出
マップリストから列抽出を行うには、Elixirでは、Enum.map を使います
これはExcelで言えば、「非表示」をしているようなイメージです
Enum.map
は、各リストを順に辿り、前回の Enum.filter
同様、fn(n) ->
の後ろの処理を実施し、その結果をリストで返します
マップの特定列を指定する ["xxx"]
を使って、複数行から、名前だけを抽出します
iex> datas |> Enum.map(fn(n) -> n["name"] end)
["en-pedasi", "zacky", "tsuchiro", "piacere"]
名前のリストが返ってきました
なお、fn(n) ->
のカッコは省略できるので、以降では下記のように省略します
iex> datas |> Enum.map(fn n -> n["name"] end)
["en-pedasi", "zacky", "tsuchiro", "piacere"]
次に、このリストで返ってきているところを、マップリストで返すようにしてみます
->
から end
の部分で、%{key => value}
で定義できるマップを作れば、マップリストになります
iex> datas |> Enum.map(fn n -> %{"name" => n["name"]} end)
[
%{"name" => "en-pedasi"},
%{"name" => "zacky"},
%{"name" => "tsuchiro"},
%{"name" => "piacere"}
]
名前に加え、年齢も抽出します
iex> datas |> Enum.map(fn n -> %{"name" => n["name"], "age" => n["age"]} end)
[
%{"age" => 54, "name" => "en-pedasi"},
%{"age" => 50, "name" => "zacky"},
%{"age" => 40, "name" => "tsuchiro"},
%{"age" => 48, "name" => "piacere"}
]
このように、Enum.map
を使って、マップリストから列抽出を行うことができます
ちなみにforを使うと…
Elixirにも for
という構文があり、これを使うと、Enum.map
と同じように列抽出ができます
iex> for n <- datas do
...> %{"name" => n["name"], "age" => n["age"]}
...> end
[
%{"age" => 54, "name" => "en-pedasi"},
%{"age" => 50, "name" => "zacky"},
%{"age" => 40, "name" => "takuto"},
%{"age" => 48, "name" => "piacere"}
]
for
の使いどころは、この後出てくるWeb表示のような、出力したら、それ以降で出力結果を使うことが無いケースが良いと思います(イマイチ、ピンと来なければ、Web表示でのみ for
を使う、と機械的に覚えておいてください)
forはforでは無い【※プログラミング経験者のみご覧を】
for
を最初に出さなかったのは、以下3つの理由からです
①既存のオブジェクト指向言語の「for」と同じでは無いから
→for
中で変数代入しても次のループではその代入※は消えてしまう
②Enum.map()のように、パイプを繋いで使えないから
③「馴染みあるせいで使いたくなる」という思考に陥ることが関数型言語に移れない罠だから
※Elixirでは、=
は「代入」では無く、「束縛」という行為になりますが、ここでは説明を割愛
恐らく for
を最初に出していたら、既存のプログラミング言語を思い出しながら、Elixirを覚えていき、その先には「Enum.map
は覚えなくても良いでは無いか」という、過去のパターンに依存した思考に及ぶ可能性が高いからです
この思考こそが、「関数型言語」という新たなパラダイムに移行することを妨げる大きな原因なのです
Phoenixのインストール
さて、ここまでで習得した「フィルタ」「並べ替え」をWeb表示してみましょう
ElixirのWebアプリケーションフレームワーク「Phoenix」をインストールします
iexをCtrl+cを2回押して抜けてから、下記コマンドを入力します
mix archive.install hex phx_new
途中で入力を求められるので、y
を押してください
Resolving Hex dependencies...
Dependency resolution completed:
New:
phx_new 1.6.15
* Getting phx_new (Hex package)
All dependencies are up to date
Compiling 11 files (.ex)
Generated phx_new app
Generated archive "phx_new-1.6.15.ez" with MIX_ENV=prod
Found existing entry: c:/Users/masq_/.mix/archives/phx_new-1.6.12
Are you sure you want to replace it with "phx_new-1.6.15.ez"? [Yn] y(←y、Enterを入力)
Phoenix PJ作成、起動
Phoenixプロジェクトを作成します(ライブラリのダウンロードとインストールも行われます)
mix phx.new basic --no-ecto
Fetch and install dependencies? [Yn] (←y、Enterを入力)
PJフォルダに入り、Phoenixサーバを起動します
cd basic
iex -S mix phx.server
下記のようなwarningが出ることがありますが、気にせず先に進んでください(Phoenixが新しいバージョンのElixirに追従できていないことが原因)
…
warning: the :gettext compiler is no longer required in your mix.exs.
Please find the following line in your mix.exs and remove the :gettext entry:
compilers: [..., :gettext, ...] ++ Mix.compilers(),
(gettext 0.20.0) lib/mix/tasks/compile.gettext.ex:5: Mix.Tasks.Compile.Gettext.run/1
(mix 1.14.0) lib/mix/task.ex:421: anonymous fn/3 in Mix.Task.run_task/4
(mix 1.14.0) lib/mix/tasks/compile.all.ex:92: Mix.Tasks.Compile.All.run_compiler/2
(mix 1.14.0) lib/mix/tasks/compile.all.ex:72: Mix.Tasks.Compile.All.compile/4
(mix 1.14.0) lib/mix/tasks/compile.all.ex:59: Mix.Tasks.Compile.All.with_logger_app/2
(mix 1.14.0) lib/mix/tasks/compile.all.ex:33: Mix.Tasks.Compile.All.run/1
…
ブラウザで「http://localhost:4000
」にアクセスすると、Phoenixで作られたデフォルトのWebページが見れます
複数列データのWeb表示
それでは、Phoenixで作ったWebページ上に、複数列データを表示してみましょう
以下のように、デフォルトのWebページを書き換えます
<%
datas = [
%{"name" => "enぺだーし", "age" => 54, "team" => "有限会社デライトシステムズ", "position" => "代表取締役、性能探求者" },
%{"name" => "ざっきー", "age" => 50, "team" => "公立大学法人 北九州市立大学", "position" => "准教授、カーネルハッカー" },
%{"name" => "たくと", "age" => 40, "team" => "カラビナテクノロジー株式会社", "position" => "リードエンジニア" },
%{"name" => "piacere", "age" => 48, "team" => "株式会社DigiDockConsulting", "position" => "常務取締役CTO、技術顧問、重力プログラマ" }
]
%>
<table class="container m-10">
<%= for n <- datas do %>
<tr>
<td><%= n["name"] %></td>
<td><%= n["age"] %></td>
<td><%= n["team"] %></td>
<td><%= n["position"] %></td>
</tr>
<% end %>
</table>
index.html.heex上では、<% ~ %>
もしくは <%= ~ %>
で囲んだ部分に、Elixirを書くことができます
ここでは、for
でデータを1行ずつ取り出すループを行い、その中で ["~"]
で各列の値を取得しています
その結果、以下のようなWebページが表示されるようになります
Phoenixロゴとバージョンが無くて寂しい方は、下記を最上部に追加してください
<div class="container m-10">
<svg viewBox="0 0 71 48" class="h-12" aria-hidden="true">
<path
d="m26.371 33.477-.552-.1c-3.92-.729-6.397-3.1-7.57-6.829-.733-2.324.597-4.035 3.035-4.148 1.995-.092 3.362 1.055 4.57 2.39 1.557 1.72 2.984 3.558 4.514 5.305 2.202 2.515 4.797 4.134 8.347 3.634 3.183-.448 5.958-1.725 8.371-3.828.363-.316.761-.592 1.144-.886l-.241-.284c-2.027.63-4.093.841-6.205.735-3.195-.16-6.24-.828-8.964-2.582-2.486-1.601-4.319-3.746-5.19-6.611-.704-2.315.736-3.934 3.135-3.6.948.133 1.746.56 2.463 1.165.583.493 1.143 1.015 1.738 1.493 2.8 2.25 6.712 2.375 10.265-.068-5.842-.026-9.817-3.24-13.308-7.313-1.366-1.594-2.7-3.216-4.095-4.785-2.698-3.036-5.692-5.71-9.79-6.623C12.8-.623 7.745.14 2.893 2.361 1.926 2.804.997 3.319 0 4.149c.494 0 .763.006 1.032 0 2.446-.064 4.28 1.023 5.602 3.024.962 1.457 1.415 3.104 1.761 4.798.513 2.515.247 5.078.544 7.605.761 6.494 4.08 11.026 10.26 13.346 2.267.852 4.591 1.135 7.172.555ZM10.751 3.852c-.976.246-1.756-.148-2.56-.962 1.377-.343 2.592-.476 3.897-.528-.107.848-.607 1.306-1.336 1.49Zm32.002 37.924c-.085-.626-.62-.901-1.04-1.228-1.857-1.446-4.03-1.958-6.333-2-1.375-.026-2.735-.128-4.031-.61-.595-.22-1.26-.505-1.244-1.272.015-.78.693-1 1.31-1.184.505-.15 1.026-.247 1.6-.382-1.46-.936-2.886-1.065-4.787-.3-2.993 1.202-5.943 1.06-8.926-.017-1.684-.608-3.179-1.563-4.735-2.408l-.043.03a2.96 2.96 0 0 0 .04-.029c-.038-.117-.107-.12-.197-.054l.122.107c1.29 2.115 3.034 3.817 5.004 5.271 3.793 2.8 7.936 4.471 12.784 3.73A66.714 66.714 0 0 1 37 40.877c1.98-.16 3.866.398 5.753.899Zm-9.14-30.345c-.105-.076-.206-.266-.42-.069 1.745 2.36 3.985 4.098 6.683 5.193 4.354 1.767 8.773 2.07 13.293.51 3.51-1.21 6.033-.028 7.343 3.38.19-3.955-2.137-6.837-5.843-7.401-2.084-.318-4.01.373-5.962.94-5.434 1.575-10.485.798-15.094-2.553Zm27.085 15.425c.708.059 1.416.123 2.124.185-1.6-1.405-3.55-1.517-5.523-1.404-3.003.17-5.167 1.903-7.14 3.972-1.739 1.824-3.31 3.87-5.903 4.604.043.078.054.117.066.117.35.005.699.021 1.047.005 3.768-.17 7.317-.965 10.14-3.7.89-.86 1.685-1.817 2.544-2.71.716-.746 1.584-1.159 2.645-1.07Zm-8.753-4.67c-2.812.246-5.254 1.409-7.548 2.943-1.766 1.18-3.654 1.738-5.776 1.37-.374-.066-.75-.114-1.124-.17l-.013.156c.135.07.265.151.405.207.354.14.702.308 1.07.395 4.083.971 7.992.474 11.516-1.803 2.221-1.435 4.521-1.707 7.013-1.336.252.038.503.083.756.107.234.022.479.255.795.003-2.179-1.574-4.526-2.096-7.094-1.872Zm-10.049-9.544c1.475.051 2.943-.142 4.486-1.059-.452.04-.643.04-.827.076-2.126.424-4.033-.04-5.733-1.383-.623-.493-1.257-.974-1.889-1.457-2.503-1.914-5.374-2.555-8.514-2.5.05.154.054.26.108.315 3.417 3.455 7.371 5.836 12.369 6.008Zm24.727 17.731c-2.114-2.097-4.952-2.367-7.578-.537 1.738.078 3.043.632 4.101 1.728.374.388.763.768 1.182 1.106 1.6 1.29 4.311 1.352 5.896.155-1.861-.726-1.861-.726-3.601-2.452Zm-21.058 16.06c-1.858-3.46-4.981-4.24-8.59-4.008a9.667 9.667 0 0 1 2.977 1.39c.84.586 1.547 1.311 2.243 2.055 1.38 1.473 3.534 2.376 4.962 2.07-.656-.412-1.238-.848-1.592-1.507Zm17.29-19.32c0-.023.001-.045.003-.068l-.006.006.006-.006-.036-.004.021.018.012.053Zm-20 14.744a7.61 7.61 0 0 0-.072-.041.127.127 0 0 0 .015.043c.005.008.038 0 .058-.002Zm-.072-.041-.008-.034-.008.01.008-.01-.022-.006.005.026.024.014Z"
fill="#FD4F00"
/>
</svg>
<h1 class="text-brand flex items-center text-sm font-semibold leading-6">
Phoenix Framework
<small class="bg-brand/5 text-[0.8125rem] ml-3 rounded-full px-2 font-medium leading-6">
v<%= Application.spec(:phoenix, :vsn) %>
</small>
</h1>
</div>
Web上での複数列データの「フィルタ」
48歳以上のデータでフィルタしてみましょう
|> Enum.filter(fn n -> n["age"] >= 48 end)
の部分が該当部分です(先頭の +
はQiitaでdiffを出すための記述なので、コードには入れないでください)
<%
datas = [
%{"name" => "enぺだーし", "age" => 54, "team" => "有限会社デライトシステムズ", "position" => "代表取締役、性能探求者" },
%{"name" => "ざっきー", "age" => 50, "team" => "公立大学法人 北九州市立大学", "position" => "准教授、カーネルハッカー" },
%{"name" => "たくと", "age" => 40, "team" => "カラビナテクノロジー株式会社", "position" => "リードエンジニア" },
%{"name" => "piacere", "age" => 48, "team" => "株式会社DigiDockConsulting", "position" => "常務取締役CTO、技術顧問、重力プログラマ" }
]
+ |> Enum.filter(fn n -> n["age"] >= 48 end)
%>
<table>
<%= for n <- datas do %>
<tr>
<td><%= n["name"] %></td>
<td><%= n["age"] %></td>
<td><%= n["team"] %></td>
<td><%= n["position"] %></td>
</tr>
<% end %>
</table>
Web上での複数列データの「並べ替え」
Enum.sort
を複数列データに対して行うには、「現在行」と「次行」の列同士を比較する必要があるため、fn
に2つの引数を指定します
|> Enum.sort(fn current, next -> current["age"] < next["age"] end)
が該当部分です(先頭の +
はQiitaでdiffを出すための記述なので、コードには入れないでください)
<%
datas = [
%{"name" => "enぺだーし", "age" => 54, "team" => "有限会社デライトシステムズ", "position" => "代表取締役、性能探求者" },
%{"name" => "ざっきー", "age" => 50, "team" => "公立大学法人 北九州市立大学", "position" => "准教授、カーネルハッカー" },
%{"name" => "たくと", "age" => 40, "team" => "カラビナテクノロジー株式会社", "position" => "リードエンジニア" },
%{"name" => "piacere", "age" => 48, "team" => "株式会社DigiDockConsulting", "position" => "常務取締役CTO、技術顧問、重力プログラマ" }
]
+ |> Enum.sort(fn current, next -> current["age"] < next["age"] end)
%>
<table>
<%= for n <- datas do %>
<tr>
<td><%= n["name"] %></td>
<td><%= n["age"] %></td>
<td><%= n["team"] %></td>
<td><%= n["position"] %></td>
</tr>
<% end %>
</table>
年齢の高い順で「並べ替え」するには、current["age"] < next["age"]
を current["age"] > next["age"]
に変えるだけです
【参考】本コラムの検証環境
本コラムは、以下環境で検証しています(恐らくUbuntu実機やMacでも動きます)
- Windows11
- WSL2/Ubuntu 20.04+Elixir 1.15.5 (Erlang/OTP 25) ※最新版のインストール手順はコチラ
- Phoenix 1.7.7
- WSL2/Ubuntu 22.04+Elixir 1.13.4 (Erlang/OTP 25)
- Phoenix 1.6.15
- WSL2/Ubuntu 20.04+Elixir 1.15.5 (Erlang/OTP 25) ※最新版のインストール手順はコチラ
- Windows 10
- 実機+Elixir 1.14.2 (Erlang/OTP 25)
- Phoenix 1.6.15
- 実機+Elixir 1.14.0 (Erlang/OTP 25)
- Phoenix 1.6.15
- WSL2/Ubuntu 20.04+Elixir 1.14.2 (Erlang/OTP 25)
- Phoenix 1.6.15
- Docker/Debian 11.6+Elixir 1.14.2 (Erlang/OTP 25)
- Phoenix 1.6.15
- WSL2/Ubuntu 18.04+Elixir 1.13.0 (Erlang/OTP 24)
- Phoenix 1.6.6
- Phoenix 1.6.2
- 実機+Elixir 1.11.3
- Phoenix 1.5.x
- 実機+Elixir 1.8.1
- Phoenix 1.4.2
- 実機+Elixir 1.6.x
- Phoenix 1.3.x
- Docker+Elixir 1.6.x
- Phoenix 1.3.x
- 実機+Elixir 1.14.2 (Erlang/OTP 25)
終わり
前回と今回で、ExcelからElixirへの読み替えを行い、Web表示まで辿り着きました
関数型言語によるWebアプリ開発が、思った以上にカンタンじゃ無いか…と感じていただけたら幸いです
なお、Web上での列抽出は、列指定が固定だと使う場面が無いので、次回以降に解説することになります
お知らせ
fukuoka.exコアメンバーのインタビュー 第3回(最終回)を公開しました
過去のインタビューは、以下にあります
インタビュー 第1回
インタビュー 第2回