はじめに
この記事は、
fukuoka.ex Elixir/Phoenix Advent Calendar 2020 の 11日目です。
https://qiita.com/advent-calendar/2020/fukuokaex
前日は @tuchiro さん
Phoenix LiveViewへのサービスリプレイスの際に既存コンテンツからの段階的な移行を考えてみる でした。
https://qiita.com/tuchiro/items/39ba2840e1cbb4567135
対象
- プロゲート辺りを一通りやってみて、Ruby PHP JS 辺りを触ってみたよという段階の方
- プログラミングElixir読んで速攻挫折しかかっている方
- 中学生、高校生などで、環境構築について知見がないし、自分のPCもないよという方
環境
今回は環境構築も省きますので、paiza.io を使います。
プログラミングElixirではiexを使うので、環境構築が必要になりますが、そもそもそれも敷居が高いとか、ちょっと試してみたいだけ・・・という方もいらっしゃるはずです。
そういう方の為に朗報・・なんとpaiza.ioに Elixir Erlang があるんですよね。(スゴイ)
前提条件
文字列や数値、配列(リスト)連想配列(Hash)などのキーワードがある程度わかる方。
今回の目的
paizaのスキルチェックにElixirで挑戦出来るようになりたい。
1 まずは動かしてみましょう
1-1. paiza.ioを立ち上げてElixirを選択します
https://paiza.io リンクはここです。
1-2. 最初のコードをかいてみましょう
こんなコードを書き込んでみて下さい。
x = "hello"
IO.puts x
で、四角で囲んだ「実行」ボタンを押すと・・
下線部分のように下窓に実行結果が出てきます。
さて、ここで IO.puts
を使っていますが、これを使うのはここまでです。
以降は、 IO.inspect
を使います。
早速書き換えてみましょう。
x = "hello"
IO.inspect x
IO.puts
を使うと、 hello
でしたが、 今回は、 "hello"
となりました。
型が分かる、よりデバッグしやすい出力となります。
2. タプル
2-1. タプルって何?
タプルは、python 等を使われている方には馴染みかもしれませんが、そうでない方にはちょっと馴染みがない型となります。
{}
で囲まれる型ですので、一瞬Hashとか連想配列的な何かと思いますが、Elixirのそれは、 %{}
のように書きますので、別物となります。
また、キーバリューの形ではなく、配列(Elixirではリスト)の形にも似ています。
例えば、
{ "tokyo", "osaka", "fukuoka" }
や、
{ :tokyo, :osaka, :fukuoka }
のように書かれたりします。(前者は要素が文字列型、後者は要素がアトム型(シンボルみたいなモノ))
ちょっと試してみましょう。
x = { "tokyo", "osaka", "fukuoka" }
IO.inspect x
このように書いてみましょう。
実行結果に {}
で囲まれた複数の要素が確認できます。
ちなみに、 IO.puts x
を使うと
エラーが出ますのでご注意を。
2-2. タプルが使われている場所
Rails辺りしか知らなくて、「いけるやろー」とPhoenix を使うと割と困るのですが、(経験談) タプルは、ファイルを読み込む時とか、ライブラリを使ってApiの結果を取得する時とか、何かしらの結果を表示した時に
{ :ok, "何かしらの結果"}
{ :error, "何かしらの結果"}
みたいな感じで出力されるようです。
2-3. 値の取り出し方
まずは、通常考えられる取り出し方。
リスト同様キーがありませんので、数字を使います。
こんなコードを書いてみましょう。
prefectures = { :tokyo, :osaka, :fukuoka }
IO.inspect elem(prefectures, 0)
IO.inspect elem(prefectures, 1)
IO.inspect elem(prefectures, 2)
と、elem/2
を使って書けます。
または、パターンマッチだと、
{ x, y, z } = { :tokyo, :osaka, :fukuoka }
IO.inspect x
IO.inspect y
IO.inspect z
こんな感じで取り出せます。
ここでまた注意です。
{ x, y, z } = { :tokyo, :osaka, :fukuoka }
IO.inspect x
このように書くと、 y
z
が使われていないので、
こんな、warning が出ます。そして、結果も出ません。
そういう場合は、
{ x, _, _z } = { :tokyo, :osaka, :fukuoka }
IO.inspect x
_
や _z
のようにアンダースコアを書くことで、パターンマッチから無視されるようになります。
パターンマッチに関しては、長くなるのでここでは割愛させていただきます。
2-4. File.read
簡単な使用例を見てみましょう。
わかりやすいのはファイルを読み込んだ時です。
paiza.io は別ファイルを作る事も出来ますので、ファイルを増やしてリネームします。
以下のファイルを作って下さい。
test.txt
%{ :tokyo => "東京", :osaka => "大阪", :fukuoka => "福岡" }
形としては、マップですが、取れるデーターは文字列となる想定です。(拡張子 exsでも同様の結果でした )
そして、 Main.exs の方はこのように書きます。
Main.exs
{ result, file } = File.read "test.txt"
IO.inspect(result)
IO.inspect(file)
実行結果は
このようになります。
次に、ファイル名を存在しないものに変更します。
Main.exs
{ result, file } = File.read "hogehoge.txt"
IO.inspect(result)
IO.inspect(file)
すると、結果は、
この通り、最初の要素で、 :ok
または、 :error
という結論を返してきて、 成功ならデーターを、 なければ :enoent
を返しています。
enoent
はファイルやディレクトリがないよ〜という意味のようです。
では、取れた結果を元にちょっとした加工をやってみましょう。
2-5. パイプ演算子
想定として、
「マップのつもりでデーター見たら文字列だったでござる」
というつもりですので、ここからパイプ演算子を使って加工していきます。
パイプ演算子については、細かい話は割愛しますが、ざっくり・・メソッドチェーンのような処理が可能になる点と、元のデーターを汚染しない点について認識していただければと思います。
{ result, file } = File.read "test.txt"
IO.inspect(result)
file
|> String.replace(~r/"| |:|%{|}/, "")
|> String.split(~r/,|=>/)
|> Enum.chunk_every(2)
|> Enum.map(fn [k, v] -> {String.to_atom(k), v} end)
|> Map.new()
このような流れで考えてみます。
ただ、このまま実行すると経過が分からないので、
{ result, file } = File.read "test.txt"
IO.inspect(result)
file
|> String.replace(~r/"| |:|%{|}/, "")
|> IO.inspect()
|> String.split(~r/,|=>/)
|> IO.inspect()
|> Enum.chunk_every(2)
|> IO.inspect()
|> Enum.map(fn [k, v] -> { String.to_atom(k), v } end)
|> IO.inspect()
|> Map.new()
|> IO.inspect()
IO.inspect(file)
このように inspect を挟んでいきましょう。
結果は見たとおり、 パイプで文字列を段階的に想定のデーターに加工ましたが、元の file
に関しては最終行で出力した通り、無加工のものとなっています。
ちなみに・・ですが、パイプ演算子を使って関数を実行している時、本来の第一引数は、省略されています。例えば、
file
|> Map.new()
だとしたら、
Map.new(file)
がそもそもの引数の入り方という事です。
これを連続した処理をするために、第一引数を先頭から引き継いだ形としてパイプ演算子を使って省略出来るようになっています。(なんかカッコいい)
3. リストとEnum.map
3-1. ゴニョゴニョするならリスト
タプルは、処理が早いそのものに要素を足したり引いたり、加工することには向いていないようです。
ですので、データーの加工に関してはリストで行う方が良いようです。
ここで、せっかくpaizaを利用しているので、スキルチェックに出てくるような入力を受け取って、任意の結果が出せるように考えてみましょう。
3-2. Enum.map
問題: ある三角形の三辺の値が改行区切りで入力されてきます。
その三角形が、
- 正三角形の場合、「正三角形です」
- 二等辺三角形の場合、「二等辺三角形です」
- その他の場合、「三角形です」
と出力しましょう。
的な出題を解こうとした時、
paiza.io には入力を取る機能もありますので、
こんな感じで、
2
2
4
と二等辺三角形となる値をセットしてみましょう。
そして、コードはこのように書いてみます。
data = [ nil, nil, nil ]
|> Enum.map(fn s -> IO.gets(s) end)
|> IO.inspect
|> Enum.map(fn s -> String.trim(s) end)
|> IO.inspect
|> Enum.map(fn s -> String.to_integer(s) end)
|> IO.inspect
|> Enum.uniq
|> IO.inspect
|> Enum.count
|> IO.inspect
result = fn
1 -> "正三角形です"
2 -> "二等辺三角形です"
3 -> "三角形です"
end
IO.puts result.(data)
実行結果は、
バッチリです。是非他の値も入れて遊んでみて下さい。
内容ですが、 IO.gets/1
で入力値を待ちますが、これを 三回分実行しています。
後は、 uniq
で重複をまとめて要素の数をカウントすると、三角形の判断が出来ます。
パイプ演算子もカッコいいのですが、
result = fn
1 -> "正三角形です"
2 -> "二等辺三角形です"
3 -> "三角形です"
end
この、関数の中に body
を複数持たせて if
を書いていない所が素敵ですよね。
ELixirは恐ろしい程 ループや条件式の記述が書かれないので、非常に面白いと感じますね。
これでpaiza序盤を少し遊ぶ事が出来るのではないでしょうか?
是非腕試ししてみて下さい!
最後に
ここまでご覧頂きましてありがとうございます。
初学者に向けたものとして、当記事のEnum辺りの処理の元となった piacere さんの記事も是非!(私自身がすごく勉強させていただきました。)
こちらは続き記事で Phoenix も扱いますよ。
Excelから関数型言語マスター1回目:データ行の”並べ替え”と”絞り込み”
https://qiita.com/piacerex/items/6714e1440e3f25fb46a1
そして、明日は @koyo-miyamura さんの記事です。