More Related Content Similar to やや関数型を意識した風Elixir/Phoenixご紹介
Similar to やや関数型を意識した風Elixir/Phoenixご紹介 (20) やや関数型を意識した風Elixir/Phoenixご紹介5. 4
1.Elixirは「データ変換」の言語:Enum
Enumモジュールで、リスト/マップ/タプルが操作できます
mapやfilter、reduce等、一通りあります
iex> Enum.sort( list )
[123, 456, true, "abc"]
iex> Enum.sort( map )
[{"key1", 123}, {"key2", "abc"}]
マップをソートするとタプルに変換される
(マップは順序を持てないため)
iex> Enum.map( list, fn( x ) -> IO.puts( "value=#{x}" ) end )
value=123
value=abc
value=456
value=true
iex> Enum.filter( list, fn( x ) -> x >= 124 end )
["abc", 456, true]
iex> Enum.reduce( list, fn( x, pre ) -> "#{x}, #{pre}" end )
"true, 456, abc, 123"
11. 10
1.Elixirは「データ変換」の言語:パターンマッチ
リストは、パターンマッチにより、「先頭」と「以降全て」で分割でき、
リスト要素の順次処理を組む土台となります
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]
…
iex> head4
[1]
iex> tail4
[]
12. 11
1.Elixirは「データ変換」の言語:パターンマッチ
バイナリデータの分割/マップ化も、パターンマッチでとてもカンタン
に書けます
defmodule MP3 do
defstruct [ :header, :frames, :other_data ]
def analisis(
<<
tag :: bitstring-size( 24 ),
version :: bitstrint-size( 16 ),
flg :: bitstring-size( 8 ),
size :: unsigned-integer-size( 32 ),
data :: binary, # 以降のバイナリデータはここに集約される
>>
) do
%MP3
{
:header MP3Header( tag, version, flg, size )
}
end
end
13. 12
1.Elixirは「データ変換」の言語:再帰(オマケ)
ループは再帰で書きます (末尾再帰は最適化される)
処理対象が残っている間、繰り返される関数と、処理対象が無
くなった際の完了処理の2つの関数を用意し、パターンマッチに
よって呼び分けが行われます
なお、Enumとパターンマッチで大抵の事は済むので、再帰を書く
機会は結構少ないです
defmodule Misc do
def nop( [ head | tail ], rows ), do: nop( tail, rows ++ [ head ] )
def nop( [], rows ), do: rows
…
# iex -S mix
iex> Misc.nop( [ 5, 8, 3, 1 ], [] )
[5, 8, 3, 1]
※一応、for構文はあるけど使う場面はほぼ無い
iex -S mixでプロジェクトのビルドが走り、iexに入ります
18. 17
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}
2.【例】JSONパーサを作る
lib/crawl.ex
22. 21
タイトル抽出を追加し、URL周りをキレイにしたら完成です
こんな感じで、たった11行のコードで、「Qiita APIを呼び、記事
タイトルをリストアップ」するアプリケーションが書けました
2.【例】JSONパーサを作る
defmodule Crawl do
def get( query "Elixir" ) do
url( query )
|> HTTPoison.get!
|> body
|> Poison.decode!
|> Enum.map( fn( %{ title: title } ) -> title end )
end
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> Crawl.get
["Advent Calendar 2016 投稿数の多いユーザランキング(上位50位)",
"スタートアップ企業の技術選定でPhoenix(Elixir)&React(JS)+時々Echo(go)を採用した",
"PhoenixでAPIによるログイン機能を実装する",
…(他記事が複数並ぶ)…
"Elixir/Phoenix環境をElasticBeanstalk Custom Platformで構築してみた"]
25. 24
3.応用:Qiita API以外のJSONパース
Qiita API以外のAPIも、ほぼ同じコードでJSONパースできます
「はてなAPI」のJSONは、若干壊れている (笑) ので、デコード
前に文字列操作で補正をかけるパイプを追加します
defmodule Hatena do
def get( query "Elixir" ) do
url( query )
|> HTTPoison.get!
|> body
|> String.slice( 1..-3 ) # そのままだとinvalidなJSONなので加工
|> Poison.decode!
|> Enum.map( fn( %{ title: title } ) -> title end )
end
def url( query ), do: "http://b.hatena.ne.jp/entrylist/json?sort=count&url=#{query}"
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
26. 25
3.応用:Qiita API以外のJSONパース
「Wikipedia API」のJSONは、Qiita APIより2段、深い階層
構造なので、titleを抽出できる階層まで下るパイプを追加します
defmodule Wikipedia do
def get( query "Elixir" ) do
url( query )
|> HTTPoison.get!
|> body
|> Poison.decode!
|> dig
|> Enum.map( fn( %{ title: title } ) -> title end )
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
def body( %{ status_code: 200, body: json_body } ), do: json_body
end
# iex -S mix
iex> Wikipedia.get
["熊沢千絵",
"プログラミング言語一覧",
…(他記事が複数並ぶ)…
"エリクサー"]
lib/wikipedia.ex
27. 26
3.応用:Qiita API以外のJSONパース
「Github API」は、検索パラメータの指定が異なるので、検索パ
ラメータに渡すデフォルト値を変えます
defmodule Github do
def get( query "Elixir-lang/Elixir" ) do # 例.rails/rails
url( query )
|> HTTPoison.get!
|> body
|> Poison.decode!
|> Enum.map( fn( %{ title: title } ) -> title end )
end
def url( query ), do: "https://api.github.com/repos/#{query}/issues"
def body( %{ status_code: 200, body: json_body } ), do: json_body
end
# iex -S mix
iex> Wikipedia.get
["Use @optional_callbacks in GenServer",
"Improve no function clause error messages",
…(他記事が複数並ぶ)…
"Make Mix more friendly for newcomers"]
lib/github.ex
30. 29
4.高速WebFW「Phoenix」
Phoenixは、Railsに似たビルドツールによる、Web (+DB)や
JSON API (+DB) の手軽な自動生成を実現しています
(RailsのコミッターがElixirの世界に参入し、RailsライクなPhoenixをコミットしている背景があります)
このコマンド5発で、Web+DBが、テーブルの構築も含め、実現
されます
JSON API+DBの場合は、上記コマンド中、下記1行が異なる
だけで、Web+DBとほぼ変わらない手順でJSON APIが構築
できます
# mix phoenix.new web --no-brunch
# cd web
# mix ecto.create
# mix phoenix.gen.html Post posts title:string body:text
# mix ecto.migrate
# iex -S mix phoenix.server
DB追加
DBに「title」「body」列を持つテーブルを定義
(同時にModelやController、Viewも定義)
DBへのテーブル構築とMVCモジュール生成
# mix phoenix.gen.json Post posts title:string body:text