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パースアプリをサクっと書いてみる」

2,610 views

Published on

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

Published in: Engineering
  • Be the first to comment

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作成 2017/10/23 ver1.5(Elixir 1.5検証済)作成
  2. 2. 1 1. Elixirを学ぶと得られること 2. DockerでElixirを使えるようにする 3. はじめてのElixirプログラミング 4. パターンマッチ 5. JSONパースに必要なライブラリ導入 6. パイプ 7. JSONパースアプリを仕上げる 8. 応用:Qiita API以外のJSONパース 9. これから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 . 以下コマンドでElixirイメージをDLしましょう (要ネット接続) > docker pull trenpixster/elixir WindowsやMacの手順は、第3回も無いため、 自信が無いときはDockerが良いかと思います
  7. 7. 6 2.DockerでElixir:ローカルPCのフォルダ共有 ローカルPCのフォルダをDockerでマウントして開発可とするため、 Docker設定「Shared Drives」でドライブ共有をONします Windowsであれば、その前にWindowsフォルダ共有のポート (TCP/445)にアクセスできるようにしておきます ※ウイルスチェッカーとWindowsファイアウォールの両方にブロック設定がある場合がある もし設定する場所が分からない 場合や、FWのポートを空けたく ない時は、Docker内部のviで 編集やコピペを行ってください
  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文で分岐するような処理が、 引数でのパターンマッチで分岐できます 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 5.JSONパースに必要なライブラリ導入
  20. 20. 19 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
  21. 21. 20 追加したモジュールを使い、Qiita API呼出と、返ってくるJSON (以下参照)のパースを行います 5.JSONパースに必要なライブラリ導入
  22. 22. 21 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
  23. 23. 22 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
  24. 24. 23 6.パイプ
  25. 25. 24 作った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" ) ) ) …
  26. 26. 25 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!
  27. 27. 26 7.JSONパースアプリを仕上げる
  28. 28. 27 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件目の記事タイトル】"} ]
  29. 29. 28 記事タイトルをパターンマッチで抽出するコードを追加します 書かれたコードを見直すと、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! |> Enum.map( fn( %{ "title" => title } ) -> title end ) end def body( %{ status_code: 200, body: json_body } ), do: json_body end ※参照:第7章 P64~66、68~69
  30. 30. 29 Qiita記事でtitleを抽出します こうして、たった15行のコードで、「Qiita APIを呼び、記事タイト ルをリストアップするアプリ」が書けました 7.JSONパースアプリを仕上げる iex> Crawl.get ["Advent Calendar 2016 投稿数の多いユーザランキング(上位50位)", "スタートアップ企業の技術選定でPhoenix(Elixir)&React(JS)+時々Echo(go)を採用した", "弊社で検討したフルスタック寄りWebアプリケーションフレームワークの比較", "PhoenixでAPIによるログイン機能を実装する", "新人(候補)を教育することになったので、弊社の教育方針と教材まとめの記事(リンク集)を作っておく", …(他記事が複数並ぶ)… "Elixir/Phoenix環境をElasticBeanstalk Custom Platformで構築してみた"]
  31. 31. 30 8.応用:Qiita API以外のJSONパース
  32. 32. 31 8.応用:Qiita API以外のJSONパース Qiita API以外のAPIも、ほぼ同じコードでJSONパースできます 「はてなAPI」のJSONは、若干壊れている (笑) ので、デコード 前に文字列操作で補正をかけるパイプを追加します defmodule Hatena do def get() do HTTPoison.get!( "http://b.hatena.ne.jp/entrylist/json?sort=count&url=Elixir" ) |> body |> String.slice( 1..-3 ) # そのままだとinvalidなJSONなので加工 |> Poison.decode! |> Enum.map( fn( %{ "title" => title } ) -> title end ) end def body( %{ status_code: 200, body: json_body } ), do: json_body end # iex -S mix iex> Hatena.get ["【翻訳】超高速なJSON APIをElixirフレームワークのPhoenixでビ...", "2015/08/22/ElixirとPhoenixとMithrilのFFスタックでChatアプリを作っ...", …(他記事が複数並ぶ)… " Elixir ご紹介 // Speaker Deck"] lib/hatena.ex
  33. 33. 32 8.応用:Qiita API以外のJSONパース 「Wikipedia API」のJSONは、Qiita APIより2段、深い階層 構造なので、titleを抽出できる階層まで下るパイプを追加します defmodule Wikipedia do def get() do HTTPoison.get!( "https://ja.wikipedia.org/w/api.php?format=json&action=query&list =search&srsearch=Elixir" |> body |> Poison.decode! |> dig |> Enum.map( fn( %{ "title" => title } ) -> title end ) end def body( %{ status_code: 200, body: json_body } ), do: json_body def dig( %{ "query" => %{ "search" => search } } ), do: search end # iex -S mix iex> Wikipedia.get ["熊沢千絵", "プログラミング言語一覧", …(他記事が複数並ぶ)… "エリクサー"] lib/wikipedia.ex
  34. 34. 33 8.応用:Qiita API以外のJSONパース 「Github API」は、検索パラメータの指定を変更します defmodule Github do def get() do HTTPoison.get!( "https://api.github.com/repos/Elixir-lang/Elixir/issues" ) |> body |> Poison.decode! |> Enum.map( fn( %{ "title" => title } ) -> title end ) end def body( %{ status_code: 200, body: json_body } ), do: json_body end # iex -S mix iex> Github.get ["Use @optional_callbacks in GenServer", "Improve no function clause error messages", …(他記事が複数並ぶ)… "Make Mix more friendly for newcomers"] lib/github.ex
  35. 35. 34 9.これからElixirを学んでいくあなたへ
  36. 36. 35 9.これからElixirを学んでいくあなたへ 今回は、Elixirの入門ということで、パターンマッチやパイプといった Elixirの基本的な機能についてご説明しました 関数型言語が、「そこまで難しく無いかも?」と思っていただけたら この入門としては大成功です Java等のオブジェクト言語に慣れた方ほど、とっつき難いところが 関数型言語にはありますが、Elixirは入門向きだと思います 逆に、プログラミングはこれから、という方にとっては、余計な知識 を入れずにElixirを直感で理解できる、良い機会です 仕事でも趣味でも、プログラミングライフをエンジョイしてください!

×