Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
Elixir入門 第1回
「パターンマッチ」&「パイプ」で
JSONパースアプリをサクっと書く
with Docker
2017/03/29 ver0.5作成
2017/04/06 ver0.9作成
2017/04/13 ver1.0作成
20...
1
1. Elixirを学ぶと得られること
2. DockerでElixirを使えるようにする
3. はじめてのElixirプログラミング
4. パターンマッチ
5. JSONパースに必要なライブラリ導入
6. パイプ
7. JSONパースアプ...
2
1.Elixirを学ぶと得られること
3
1.Elixirを学ぶと得られること
を学ぶと、以下のような苦難から解放されます
a. C10K問題などのソフトウェア性能問題からの解放
b. JSONパーサを手続き的にチマチマ書くことからの解放
c. オブジェクト設計という「余計な仕事」...
4
2.DockerでElixirを使えるようにする
5
2.DockerでElixirを使えるようにする
Elixirを使い始めるのに、3種類の方法があります
① yumやapt/Homebrew/Chocolateyを使う
② 各OS用のElixirをダウンロード、インストールする
③ Doc...
6
2.DockerでElixir:ローカルPCのフォルダ共有
ローカルPCのフォルダをDockerでマウントして開発可能とするた
め、Docker設定「Shared Drives」でドライブ共有をONします
Windowsであれば、その前にW...
7
2.DockerでElixir:Elixirの起動
以下コマンドで、Docker上のElixirイメージを起動します
※紫字の部分は、【ローカルPCの共有したいパス】:【Docker上でマウントされるパス】
ElixirのREPL (対話シ...
8
2.DockerでElixir:REPLでのElixir
REPL内でElixirの簡単なプログラミングができます
iexから抜けてbashに戻るには、Ctrl+cを2回入力します
iex(1)> 1+2
3
iex(2)> list = ...
9
3.はじめてのElixirプログラミング
10
3.はじめてのElixirプログラミング:新規PJ作成
以下コマンドでElixirのプロジェクトを作成します
※紫字の部分は、作成するプロジェクト名 (全部、小文字で打つこと)
以下のようなフォルダ/ファイルが作られます
プログラミングす...
11
3.はじめてのElixirプログラミング:ビルド、実行
lib/Misc.exを作り、お好きなエディタで以下を入力します
ファイル保存したら、以下コマンドでビルドします
iexが起動されるので、以下のように作成した関数を実行します
def...
12
3.はじめてのElixirプログラミング:1行関数
関数の呼出時、”()”は省略できます
関数は、以下のように1行でも書け、endも省略できます
リストだけで無く、マップも同じ関数定義でソートできます
(キー名でソートがかかります)
de...
13
4.パターンマッチ
14
4.パターンマッチ:引数でのマッチ
いよいよ、他の言語に無い、Elixirのパワフルな面を見ていきます
下記赤囲みの関数を追加してください
以下のように入力して、作成した関数を試します
マップの”k2”キーの値抽出を、ナント引数だけで実装...
15
4.パターンマッチ:複数関数の呼び分け
キーだけで無く、値でマッチしての関数の呼び分けも可能です
以下のように、上と下の関数が、値に応じて、呼び分けされます
他の言語なら、関数呼び出し後にif文で分岐するような処理が、
引数でのパターンマ...
16
4.パターンマッチ:マッチしない場合
該当キーがマップ内に存在しない場合、マッチエラーになります
引数に”_”を指定すると、その他としてマッチできるようになります
defmodule Misc do
def match( %{ Yes: ...
17
4.パターンマッチ:関数内でのマッチ
もし、関数内でマッチさせるなら、以下のような書き方になります
(ただ、関数の引数でマッチする書き方の方が推奨です)
実行結果は、引数パターンマッチ版と同じです
defmodule Misc do
de...
18
4.パターンマッチ:リストのパターンマッチ
リストは、先頭を「head」、以降を「tail」でパターンマッチできます
※参照:第7章 P64~66、68~69
iex> [ head1 | tail1 ] = [ 5, 8, 3, 1 ]...
19
4.パターンマッチ:リストのパターンマッチ
head/tailにより、リストの巡回が可能です
リストの要素を先頭から順に拾って繋げるだけなので、引数と全く
変わらないリストが返ります
※参照:第7章 P64~66、68~69
defmod...
20
5.JSONパースに必要なライブラリ導入
21
5.JSONパースに必要なライブラリ導入
JSONパーサアプリを書く前段として、ElixirのHTTPclientであ
るHTTPoisonと、JSONparserであるPoisonを導入します
まず、mix.exsに下記赤囲み部分を追加...
22
追加したモジュールを使い、Qiita API呼出と、返ってくるJSON
(以下参照)のパースを行います
5.JSONパースに必要なライブラリ導入
23
lib/Crawl.exを作り、HTTPoisonでQiita APIの呼び出しを
行うコードを書きます
実行すると、Qiitaから取得したJSONが出力されます
前ページで見たBody部だけで無く、StatusCode等も見れます
de...
24
5.JSONパースに必要なライブラリ導入
StatusCodeが取得成功を示す「200」のときだけ、Body部を
抜き出し、パターンマッチしやすい変換を行います
変換後のBody部だけが出力されます
defmodule Crawl do
...
25
6.パイプ
26
作ったget()を見ると、Qiita取得→Body抽出→JSONデコード
という流れを、変数で受け渡しており、まどろっこしいです
一方、変数を無くすと、流れと逆になるため、読みにくいです
defmodule Crawl do
def ge...
27
Elixirでは、これを直感的かつスマートに書けて、ステキです
"|>"はパイプ演算子と呼ばれ、前の処理の出力を、次の処理
の第1引数として暗黙で渡します
引数が複数ある場合は、第2引数以降を()で指定も可能です
※下記はサンプルなので動...
28
7.JSONパースアプリを仕上げる
29
7.JSONパースアプリを仕上げる
これまで作成したコードをベースに、「Qiita APIを呼び、記事タイ
トルをリストアップするアプリ」に仕上げていきましょう
Poison.decodeでパターンマッチしやすく変換されたBodyは、
以...
30
記事タイトルを抽出するコードを追加します
上のtitle_list()は、変換されたBodyのリストを第1引数で受け、
抽出したタイトルを溜め込むリストを第2引数としています
7.JSONパースアプリを仕上げる
defmodule Cra...
31
Bodyリストの先頭マップを「head」、以降のマップ群を「tail」で
扱えるようにし、head内に存在する”title”キーの値、つまり記事
タイトルをパターンマッチで抽出し、「json_title」に持たせます
7.JSONパースア...
32
抽出したタイトルを”[”と”]”で囲むことでリスト化し、”++”でタイ
トルを溜め込むリストである「titles」の先頭に連結します
なお、初回時は、titlesには空リストである[]が渡されています
7.JSONパースアプリを仕上げる:...
33
以降のマップ群「tail」に対し、引き続き、抽出を行うため、
title_list()を再度呼びます
tailが空リストになったら、下のtitle_list()が呼ばれ、完了です
7.JSONパースアプリを仕上げる:再帰リスト処理
def...
34
titleの抽出を、まず簡単なデータでテストします
うまく抽出できました
次に、Qiita記事でtitleを抽出します
こうして、たった15行のコードで、「Qiita APIを呼び、記事タイト
ルをリストアップするアプリ」が書けました
7...
35
書かれたコードを見直すと、タイトル抽出は比較的難しいものの、
メインの処理は、JSON取得→Body抽出→JSONデコード→
複数title抽出と、とても直感的で分かりやすいコードです
7.JSONパースアプリを仕上げる
defmodul...
36
8.応用① コードを洗練させる
37
より直感的なコードに改善できます
第1引数でBodyのみ受けるtitle_list()を設けることで、最初は
空が確定している空リストの指定が省略できます
8.応用① コードを洗練させる:自明な引数の省略
defmodule Crawl ...
38
URL指定も関数化し、検索キーワードもパラメータ化します
パイプで簡単に処理を継ぎ足せるのが、Elixirらしいところです
8.応用① コードを洗練させる:URL指定の分離
defmodule Crawl do
def get( quer...
39
「head」と「tail」を関数内でパターンマッチしている部分を、引数
で”title”を直接パターンマッチするようにし、title連結部分も”|”
でのリスト連結に変えれば、よりコードが短縮できます
※やや難しいので、今はニュアンスを理...
40
8.応用① コードを洗練させる:モジュール分離
共通ロジックを別モジュールに括りだします
Parse.exを以下内容で作成し、Crawl.ex側も修正します
defmodule Parse do
def body( %{ status_c...
41
9.応用② Qiita API以外のJSONパース
42
9.応用② Qiita API以外のJSONパース
Qiita API以外のAPIも、ほぼ同じコードでJSONパースできます
「はてなAPI」のJSONは、若干壊れている (笑) ので、デコード
前に文字列操作で補正をかけるパイプを追加し...
43
9.応用② Qiita API以外のJSONパース
「Wikipedia API」のJSONは、Qiita APIより2段、深い階層
構造なので、titleを抽出できる階層まで下るパイプを追加します
defmodule Wikipedia...
44
9.応用② Qiita API以外のJSONパース
「Github API」は、検索パラメータの指定が異なるので、検索パ
ラメータに渡すデフォルト値を変えます
defmodule Github do
def get( query  "El...
45
10.これからElixirを学んでいくあなたへ
46
10.これからElixirを学んでいくあなたへ
今回は、Elixirの入門ということで、パターンマッチやパイプといった
Elixirの基本的な機能についてご説明しました
関数型言語が、「そこまで難しく無いかも?」と思っていただけたら
この...
Upcoming SlideShare
Loading in …5
×

Elixir入門「第1回:パターンマッチ&パイプでJSONパースアプリをサクっと書いてみる」【旧版】※新版あります

3,198 views

Published on

※本スライド、旧版のため、下記最新版をご覧ください
https://www.slideshare.net/piacere_ex/elixir1json-81100124

Elixirの「パターンマッチ」と「パイプ」、各種ライブラリを使って、JSON APIを叩き、JSONをパースするアプリをサクっと書いてみます

Published in: Engineering

Elixir入門「第1回:パターンマッチ&パイプでJSONパースアプリをサクっと書いてみる」【旧版】※新版あります

  1. 1. Elixir入門 第1回 「パターンマッチ」&「パイプ」で JSONパースアプリをサクっと書く with Docker 2017/03/29 ver0.5作成 2017/04/06 ver0.9作成 2017/04/13 ver1.0作成 2017/06/07 ver1.1作成
  2. 2. 1 1. Elixirを学ぶと得られること 2. DockerでElixirを使えるようにする 3. はじめてのElixirプログラミング 4. パターンマッチ 5. JSONパースに必要なライブラリ導入 6. パイプ 7. JSONパースアプリを仕上げる 8. 応用①:コードを洗練させる 9. 応用②:Qiita API以外のJSONパース 10.これからElixirを学んでいくあなたへ 目次 ※ページ最下部の章番号/ページは、Dave Thomas著「プログラミングElixir」の関連記載です。ご参考ください
  3. 3. 2 1.Elixirを学ぶと得られること
  4. 4. 3 1.Elixirを学ぶと得られること を学ぶと、以下のような苦難から解放されます a. C10K問題などのソフトウェア性能問題からの解放 b. JSONパーサを手続き的にチマチマ書くことからの解放 c. オブジェクト設計という「余計な仕事」からの解放 d. 他の関数型言語での脱落からの解放 e. 複雑なマルチスレッドプログラミング/デバッグからの解放 f. 1958年に生まれた素晴らしいプログラミングパラダイムを 使いこなせなかった呪いからの解放 結果、プログラミングやプログラマ/エンジニアとしての普段の仕事 が、とても楽しくなります
  5. 5. 4 2.DockerでElixirを使えるようにする
  6. 6. 5 2.DockerでElixirを使えるようにする Elixirを使い始めるのに、3種類の方法があります ① yumやapt/Homebrew/Chocolateyを使う ② 各OS用のElixirをダウンロード、インストールする ③ DockerでElixirイメージをインスト―ル(pull)する このうち、最もてっとり早いのは、③のDockerを使うことです (OS毎の差異影響も受けないのでオススメ) Dockerをインストールしてみましょう https://www.docker.com/get-docker . Dockerが入ったら、以下コマンドでElixirイメージをDLしましょう > docker pull trenpixster/elixir
  7. 7. 6 2.DockerでElixir:ローカルPCのフォルダ共有 ローカルPCのフォルダをDockerでマウントして開発可能とするた め、Docker設定「Shared Drives」でドライブ共有をONします Windowsであれば、その前にWindowsフォルダ共有のポート (TCP/445)にアクセスできるようにしておきます ※ウイルスチェッカーとWindowsファイアウォールの両方にブロック設定がある場合がある
  8. 8. 7 2.DockerでElixir:Elixirの起動 以下コマンドで、Docker上のElixirイメージを起動します ※紫字の部分は、【ローカルPCの共有したいパス】:【Docker上でマウントされるパス】 ElixirのREPL (対話シェル) である「iex」を起動します > docker run --rm -v /c/piacere/code:/code -i -t trenpixster/elixir /bin/bash root@0e27c970ec14:/code# Docker上のElixirイメージ 内にあるbashが起動した # iex Erlang/OTP 19 [erts-8.2] [source-fbd2db2] [64-bit] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false] Interactive Elixir (1.4.1) - press Ctrl+C to exit (type h() ENTER for help) iex(1)> ※参照:第1章 P4
  9. 9. 8 2.DockerでElixir:REPLでのElixir REPL内でElixirの簡単なプログラミングができます iexから抜けてbashに戻るには、Ctrl+cを2回入力します iex(1)> 1+2 3 iex(2)> list = [ 123, "abc", 456, true ] [123, "abc", 456, true] iex(3)> Enum.sort( list ) [123, 456, true, "abc"] iex(4)> map = %{ "key2" => "abc", "key1" => 123 } %{"key1" => 123, "key2" => "abc"} iex(5)> Enum.sort( map ) [{"key1", 123}, {"key2", "abc"}] iex(6)> BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded (v)ersion (k)ill (D)b-tables (d)istribution # リストは[]で囲む マップは%{}で囲む 2種類書き方がある ①%{k1: "v1"…} ②%{"k1" => "v1"…}
  10. 10. 9 3.はじめてのElixirプログラミング
  11. 11. 10 3.はじめてのElixirプログラミング:新規PJ作成 以下コマンドでElixirのプロジェクトを作成します ※紫字の部分は、作成するプロジェクト名 (全部、小文字で打つこと) 以下のようなフォルダ/ファイルが作られます プログラミングするソースコードは、libフォルダ配下に作ります # mix new crawl # pwd;find . | sort | sed '1d;s/^.//;s//([^/]*)$/|--1/;s//[^/|]*/| /g' /code/sample |--_build |--config |--deps |--lib |--mix.exs |--README.md |--test mixはビルドツール ※Railsのbundler+rake的なもの ※参照:第13章 P131
  12. 12. 11 3.はじめてのElixirプログラミング:ビルド、実行 lib/Misc.exを作り、お好きなエディタで以下を入力します ファイル保存したら、以下コマンドでビルドします iexが起動されるので、以下のように作成した関数を実行します defmodule Misc do def sort( values ) do Enum.sort(values ) end end # cd crawl # iex –S mix ーS:フォルダ配下のScript実行 mix:Script名 ※参照:第6章 P45~46、第7章 P63 Elixirは関数をモジュール内に定義します defmodule~end:モジュール定義 def~end:関数定義 iex> Misc.sort( [ 5, 1, 3 ] ) [1, 3, 5] リスト等のコレクションをソートする
  13. 13. 12 3.はじめてのElixirプログラミング:1行関数 関数の呼出時、”()”は省略できます 関数は、以下のように1行でも書け、endも省略できます リストだけで無く、マップも同じ関数定義でソートできます (キー名でソートがかかります) defmodule Misc do def sort( values ), do: Enum.sort( values ) end ※参照:第6章 P47、第8章 P78 iex> recompile() iex> Misc.sort %{ "key2" => "abc", "key1" => 123 } [{"key1", 123}, {"key2", "abc"}] iex> Misc.sort %{ No: "no need", Yes: "need", NA: "N/A" } [NA: "N/A", No: "no need", Yes: "need"] iex> Misc.sort [ 5, 1, 3 ] [1, 3, 5] iexを抜けずにコード変更を反映できる
  14. 14. 13 4.パターンマッチ
  15. 15. 14 4.パターンマッチ:引数でのマッチ いよいよ、他の言語に無い、Elixirのパワフルな面を見ていきます 下記赤囲みの関数を追加してください 以下のように入力して、作成した関数を試します マップの”k2”キーの値抽出を、ナント引数だけで実装できます 他の言語では、そもそも引数では定義不可で、ロジックとして書く のも骨が折れますが、Elixirなら非常にカンタンです defmodule Misc do def match_sample( %{ k2: value } ), do: value … iex> recompile() iex> Misc.match_sample %{ k1: "v1", k2: "v2", k3: "v3" } "v2" ※参照:第6章 P47~49、第8章 P80
  16. 16. 15 4.パターンマッチ:複数関数の呼び分け キーだけで無く、値でマッチしての関数の呼び分けも可能です 以下のように、上と下の関数が、値に応じて、呼び分けされます 他の言語なら、関数呼び出し後にif文で分岐するような処理が、 引数でのパターンマッチで分岐できます (Elixirでも、if文は一応使えるのですが、パターンマッチで片付くので、使う場面があまり無いです) defmodule Misc do def match( %{ Yes: "we can" } ), do: "Barack Obama" def match( %{ Yes: need } ), do: need … iex> recompile() iex> Misc.match %{ No: "-", Yes: "we can", NA: "N/A" } "Barack Obama" iex> Misc.match %{ No: "-", Yes: "we do", NA: "N/A" } "we do" ※参照:第6章 P47~48、第8章 P80
  17. 17. 16 4.パターンマッチ:マッチしない場合 該当キーがマップ内に存在しない場合、マッチエラーになります 引数に”_”を指定すると、その他としてマッチできるようになります defmodule Misc do def match( %{ Yes: "we can" } ), do: "Barack Obama" def match( %{ Yes: need } ), do: need def match( _ ), do: "Yes...NOT EXIST" … iex> Misc.match( %{ No: "-", NA: "N/A" } ) ** (FunctionClauseError) no function clause matching in Crawl.match/1 (crawl) lib/Qiita.ex:5: Crawl.match(%{NA: "N/A", No: "-"}) ※参照:第6章 P47~48 iex> recompile() iex> Misc.match %{ No: "-", NA: "N/A" } "Yes...NOT EXIST"
  18. 18. 17 4.パターンマッチ:関数内でのマッチ もし、関数内でマッチさせるなら、以下のような書き方になります (ただ、関数の引数でマッチする書き方の方が推奨です) 実行結果は、引数パターンマッチ版と同じです defmodule Misc do def match_inner( input_map ) do %{ Yes: need } = input_map need end … iex> recompile() iex> Misc.match_inner %{ No: "-", Yes: "we can", NA: "N/A" } "we can" ※参照:第4章 P27
  19. 19. 18 4.パターンマッチ:リストのパターンマッチ リストは、先頭を「head」、以降を「tail」でパターンマッチできます ※参照:第7章 P64~66、68~69 iex> [ head1 | tail1 ] = [ 5, 8, 3, 1 ] [5, 8, 3, 1] iex> head1 5 iex> tail1 [8, 3, 1] iex> [ head2 | tail2 ] = tail1 [8, 3, 1] iex> head2 8 iex> tail2 [3, 1] …
  20. 20. 19 4.パターンマッチ:リストのパターンマッチ head/tailにより、リストの巡回が可能です リストの要素を先頭から順に拾って繋げるだけなので、引数と全く 変わらないリストが返ります ※参照:第7章 P64~66、68~69 defmodule Misc do def nop( [ head | tail ], rows ), do: nop( tail, rows ++ [ head ] ) def nop( [], rows ), do: rows … iex> recompile() iex> Misc.nop( [ 5, 8, 3, 1 ], [] ) [5, 8, 3, 1]
  21. 21. 20 5.JSONパースに必要なライブラリ導入
  22. 22. 21 5.JSONパースに必要なライブラリ導入 JSONパーサアプリを書く前段として、ElixirのHTTPclientであ るHTTPoisonと、JSONparserであるPoisonを導入します まず、mix.exsに下記赤囲み部分を追加してください 次に、以下コマンドで各モジュールを取得します(要ネット接続) defmodule Crawl.Mixfile do … defp deps do [ { :httpoison, "~> 0.7.2" }, { :poison, "~> 1.5" } ] end end # mix deps.get ※参照:第13章 P137~143
  23. 23. 22 追加したモジュールを使い、Qiita API呼出と、返ってくるJSON (以下参照)のパースを行います 5.JSONパースに必要なライブラリ導入
  24. 24. 23 lib/Crawl.exを作り、HTTPoisonでQiita APIの呼び出しを 行うコードを書きます 実行すると、Qiitaから取得したJSONが出力されます 前ページで見たBody部だけで無く、StatusCode等も見れます defmodule Crawl do def get() do HTTPoison.get!( "https://qiita.com/api/v2/items?query=Elixir" ) end end # iex –S mix iex> Crawl.get %HTTPoison.Response{body: "[{"rendered_body":"nu003ch2u003en … ,"title":"AWS EC2にElixir/Phoenixをインストール","updated_at":"2017-02-10T13:08:41+09:00", …(他記事が複数並ぶ)… "twitter_screen_name":null,"website_url":null}}]", …(Body部以外のHeader等が複数並ぶ)… {"Content-Length", "4293"}, {"Connection", "keep-alive"}], status_code: 200} 5.JSONパースに必要なライブラリ導入 ※参照:第13章 P137~143
  25. 25. 24 5.JSONパースに必要なライブラリ導入 StatusCodeが取得成功を示す「200」のときだけ、Body部を 抜き出し、パターンマッチしやすい変換を行います 変換後のBody部だけが出力されます defmodule Crawl do def get() do response = HTTPoison.get!( "https://qiita.com/api/v2/items?query= Elixir" ) body = body( response ) Poison.decode!( body ) end def body( %{ status_code: 200, body: json_body } ), do: json_body end iex> recompile() iex> Crawl.get [%{"body" => "# ElasticBeanstalk Custom Platformとは?nn今までのElasticBeanstalkでは事前定義済み … "title" => "AWS EC2にElixir/Phoenixをインストール", …(他記事が複数並ぶ)… "twitter_screen_name" => nil, "website_url" => nil}}] ※参照:第13章 P137~143
  26. 26. 25 6.パイプ
  27. 27. 26 作ったget()を見ると、Qiita取得→Body抽出→JSONデコード という流れを、変数で受け渡しており、まどろっこしいです 一方、変数を無くすと、流れと逆になるため、読みにくいです defmodule Crawl do def get() do response = HTTPoison.get!( "https://qiita.com/api/v2/items?query= Elixir" ) body = body( response ) Poison.decode!( body ) end … ※参照:第6章 P54~55 6.パイプ defmodule Crawl do def get() do Poison.decode!( body( HTTPoison.get!( "https://qiita.com/api/v2/items? query=Elixir" ) ) ) …
  28. 28. 27 Elixirでは、これを直感的かつスマートに書けて、ステキです "|>"はパイプ演算子と呼ばれ、前の処理の出力を、次の処理 の第1引数として暗黙で渡します 引数が複数ある場合は、第2引数以降を()で指定も可能です ※下記はサンプルなので動きません defmodule Crawl do def get() do HTTPoison.get!( "https://qiita.com/api/v2/items?query=Elixir" ) |> body |> Poison.decode! end … 6.パイプ ※参照:第6章 P54~55 HTTPoison.get!( "https://qiita.com/api/v2/items?query=Elixir" ) |> body( %{ status_code: _ }, "ERROR: not 200" ) |> Poison.decode!
  29. 29. 28 7.JSONパースアプリを仕上げる
  30. 30. 29 7.JSONパースアプリを仕上げる これまで作成したコードをベースに、「Qiita APIを呼び、記事タイ トルをリストアップするアプリ」に仕上げていきましょう Poison.decodeでパターンマッチしやすく変換されたBodyは、 以下のような、「1記事1マップ」をリストでまとめたフォーマットに なっています 記事タイトルのリストアップには、このリストを巡回し、各マップの titleを抽出すればOKです [ %{"body" => "【1件目の記事内容】", "created_at" => "【作成日】", "title" => "【1件目の記事タイトル】"}, %{"body" => "【2件目の記事内容】", "created_at" => "【作成日】", "title" => "【2件目の記事タイトル】"}, … %{"body" => "【n件目の記事内容】", "created_at" => "【作成日】", "title" => "【n件目の記事タイトル】"} ]
  31. 31. 30 記事タイトルを抽出するコードを追加します 上のtitle_list()は、変換されたBodyのリストを第1引数で受け、 抽出したタイトルを溜め込むリストを第2引数としています 7.JSONパースアプリを仕上げる defmodule Crawl do def get() do HTTPoison.get!( "https://qiita.com/api/v2/items?query=Elixir" ) |> body |> Poison.decode! |> title_list( [] ) end def body( %{ status_code: 200, body: json_body } ), do: json_body def title_list( [ head | tail ], titles ) do %{ "title" => json_title } = head added_titles = [ json_title ] ++ titles title_list( tail, added_titles ) end def title_list( [], titles ), do: titles end ※参照:第7章 P64~66、68~69
  32. 32. 31 Bodyリストの先頭マップを「head」、以降のマップ群を「tail」で 扱えるようにし、head内に存在する”title”キーの値、つまり記事 タイトルをパターンマッチで抽出し、「json_title」に持たせます 7.JSONパースアプリを仕上げる:タイトルの抽出 defmodule Crawl do def get() do HTTPoison.get!( "https://qiita.com/api/v2/items?query=Elixir" ) |> body |> Poison.decode! |> title_list( [] ) end def body( %{ status_code: 200, body: json_body } ), do: json_body def title_list( [ head | tail ], titles ) do %{ "title" => json_title } = head added_titles = [ json_title ] ++ titles title_list( tail, added_titles ) end def title_list( [], titles ), do: titles end ※参照:第4章 P27
  33. 33. 32 抽出したタイトルを”[”と”]”で囲むことでリスト化し、”++”でタイ トルを溜め込むリストである「titles」の先頭に連結します なお、初回時は、titlesには空リストである[]が渡されています 7.JSONパースアプリを仕上げる:タイトルの連結 defmodule Crawl do def get() do HTTPoison.get!( "https://qiita.com/api/v2/items?query=Elixir" ) |> body |> Poison.decode! |> title_list( [] ) end def body( %{ status_code: 200, body: json_body } ), do: json_body def title_list( [ head | tail ], titles ) do %{ "title" => json_title } = head added_titles = [ json_title ] ++ titles title_list( tail, added_titles ) end def title_list( [], titles ), do: titles end ※参照:第7章 P75
  34. 34. 33 以降のマップ群「tail」に対し、引き続き、抽出を行うため、 title_list()を再度呼びます tailが空リストになったら、下のtitle_list()が呼ばれ、完了です 7.JSONパースアプリを仕上げる:再帰リスト処理 defmodule Crawl do def get() do HTTPoison.get!( "https://qiita.com/api/v2/items?query=Elixir" ) |> body |> Poison.decode! |> title_list( [] ) end def body( %{ status_code: 200, body: json_body } ), do: json_body def title_list( [ head | tail ], titles ) do %{ "title" => json_title } = head added_titles = [ json_title ] ++ titles title_list( tail, added_titles ) end def title_list( [], titles ), do: titles end ※参照:第7章 P68~69
  35. 35. 34 titleの抽出を、まず簡単なデータでテストします うまく抽出できました 次に、Qiita記事でtitleを抽出します こうして、たった15行のコードで、「Qiita APIを呼び、記事タイト ルをリストアップするアプリ」が書けました 7.JSONパースアプリを仕上げる iex> recompile() iex> Crawl.title_list [ %{ "body" => "b1", "title" => "t1" }, %{ "body" => "b2", "title" => "t2" } ] ["t2", "t1"] iex> Crawl.get ["Advent Calendar 2016 投稿数の多いユーザランキング(上位50位)", "スタートアップ企業の技術選定でPhoenix(Elixir)&React(JS)+時々Echo(go)を採用した", "弊社で検討したフルスタック寄りWebアプリケーションフレームワークの比較", "PhoenixでAPIによるログイン機能を実装する", "新人(候補)を教育することになったので、弊社の教育方針と教材まとめの記事(リンク集)を作っておく", …(他記事が複数並ぶ)… "Elixir/Phoenix環境をElasticBeanstalk Custom Platformで構築してみた"]
  36. 36. 35 書かれたコードを見直すと、タイトル抽出は比較的難しいものの、 メインの処理は、JSON取得→Body抽出→JSONデコード→ 複数title抽出と、とても直感的で分かりやすいコードです 7.JSONパースアプリを仕上げる defmodule Crawl do def get() do HTTPoison.get!( "https://qiita.com/api/v2/items?query=Elixir" ) |> body |> Poison.decode! |> title_list( [] ) end def body( %{ status_code: 200, body: json_body } ), do: json_body def title_list( [ head | tail ], titles ) do %{ "title" => json_title } = head added_titles = [ json_title ] ++ titles title_list( tail, added_titles ) end def title_list( [], titles ), do: titles end
  37. 37. 36 8.応用① コードを洗練させる
  38. 38. 37 より直感的なコードに改善できます 第1引数でBodyのみ受けるtitle_list()を設けることで、最初は 空が確定している空リストの指定が省略できます 8.応用① コードを洗練させる:自明な引数の省略 defmodule Crawl do def get() do HTTPoison.get!( "https://qiita.com/api/v2/items?query=Elixir" ) |> body |> Poison.decode! |> title_list end def body( %{ status_code: 200, body: json_body } ), do: json_body def title_list( body ), do: _title_list( body, [] ) defp _title_list( [ head | tail ], titles ) do %{ "title" => json_title } = head added_titles = [ json_title ] ++ titles _title_list( tail, added_titles ) end defp _title_list( [], titles ), do: titles end ※参照:第7章 P69
  39. 39. 38 URL指定も関数化し、検索キーワードもパラメータ化します パイプで簡単に処理を継ぎ足せるのが、Elixirらしいところです 8.応用① コードを洗練させる:URL指定の分離 defmodule Crawl do def get( query "Elixir" ) do url( query ) |> HTTPoison.get! |> body |> Poison.decode! |> title_list end def body( %{ status_code: 200, body: json_body } ), do: json_body def title_list( body ), do: _title_list( body, [] ) defp _title_list( [ head | tail ], titles ) do %{ "title" => json_title } = head added_titles = [ json_title ] ++ titles _title_list( tail, added_titles ) end defp _title_list( [], titles ), do: titles def url( query ), do: "https://qiita.com/api/v2/items?query=#{query}" end #{~}で、引数や変数の値を展開できる ””でパラメータ未指定時のデフォルト引数が指定可能
  40. 40. 39 「head」と「tail」を関数内でパターンマッチしている部分を、引数 で”title”を直接パターンマッチするようにし、title連結部分も”|” でのリスト連結に変えれば、よりコードが短縮できます ※やや難しいので、今はニュアンスを理解する程度でもOKです 8.応用① コードを洗練させる:リスト巡回の短縮 defmodule Crawl do def get( query "Elixir" ) do url( query ) |> HTTPoison.get! |> body |> Poison.decode! |> title_list end def body( %{ status_code: 200, body: json_body } ), do: json_body 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: Enum.reverse( titles ) def url( query ), do: "https://qiita.com/api/v2/items?query=#{query}" end ※参照:第6章 P54
  41. 41. 40 8.応用① コードを洗練させる:モジュール分離 共通ロジックを別モジュールに括りだします Parse.exを以下内容で作成し、Crawl.ex側も修正します defmodule Parse do def body( %{ status_code: 200, body: json_body } ), do: json_body def title_list( body ), do: _title_list( body, [] ) defp _title_list( [], titles ), do: titles defp _title_list( [ %{ "title" => json_title } | tail ], titles ) do _title_list( tail, [ json_title | titles ] ) end end Parse.ex Crawl.ex defmodule Crawl do def get( query "Elixir" ) do url( query ) |> HTTPoison.get! |> Parse.body |> Poison.decode! |> Parse.title_list end def url( query ), do: "https://qiita.com/api/v2/items?query=#{query}" end
  42. 42. 41 9.応用② Qiita API以外のJSONパース
  43. 43. 42 9.応用② Qiita API以外のJSONパース Qiita API以外のAPIも、ほぼ同じコードでJSONパースできます 「はてなAPI」のJSONは、若干壊れている (笑) ので、デコード 前に文字列操作で補正をかけるパイプを追加します defmodule Hatena do def get( query "Elixir" ) do url( query ) |> HTTPoison.get! |> Parse.body |> String.slice( 1..-3 ) # そのままだとinvalidなJSONなので加工 |> Poison.decode! |> Parse.title_list end def url( query ), do: "http://b.hatena.ne.jp/entrylist/json?sort=count&url=#{query}" end Hatena.ex # iex -S mix iex> Hatena.get ["【翻訳】超高速なJSON APIをElixirフレームワークのPhoenixでビ...", "2015/08/22/ElixirとPhoenixとMithrilのFFスタックでChatアプリを作っ...", …(他記事が複数並ぶ)… " Elixir ご紹介 // Speaker Deck"]
  44. 44. 43 9.応用② Qiita API以外のJSONパース 「Wikipedia API」のJSONは、Qiita APIより2段、深い階層 構造なので、titleを抽出できる階層まで下るパイプを追加します defmodule Wikipedia do def get( query "Elixir" ) do url( query ) |> HTTPoison.get! |> Parse.body |> Poison.decode! |> dig |> Parse.title_list end def url( query ), do: "https://ja.wikipedia.org/w/api.php?format=json&action=query &list=search&srsearch=#{query}" def dig( %{ "query" => %{ "search" => search } } ), do: search end Wikipedia.ex # iex -S mix iex> Wikipedia.get ["熊沢千絵", "プログラミング言語一覧", …(他記事が複数並ぶ)… "エリクサー"]
  45. 45. 44 9.応用② Qiita API以外のJSONパース 「Github API」は、検索パラメータの指定が異なるので、検索パ ラメータに渡すデフォルト値を変えます defmodule Github do def get( query "Elixir-lang/Elixir" ) do # 例.rails/rails url( query ) |> HTTPoison.get! |> Parse.body |> Poison.decode! |> Parse.title_list end def url( query ), do: "https://api.github.com/repos/#{query}/issues" end Github.ex # iex -S mix iex> Wikipedia.get ["Use @optional_callbacks in GenServer", "Improve no function clause error messages", …(他記事が複数並ぶ)… "Make Mix more friendly for newcomers"]
  46. 46. 45 10.これからElixirを学んでいくあなたへ
  47. 47. 46 10.これからElixirを学んでいくあなたへ 今回は、Elixirの入門ということで、パターンマッチやパイプといった Elixirの基本的な機能についてご説明しました 関数型言語が、「そこまで難しく無いかも?」と思っていただけたら この入門としては大成功です Java等のオブジェクト言語に慣れた方ほど、とっつき難いところが 関数型言語にはありますが、Elixirは入門向きだと思います 逆に、プログラミングはこれから、という方にとっては、余計な知識 を入れずにElixirを直感で理解できる、良い機会です 仕事でも趣味でも、プログラミングライフをエンジョイしてください!

×