Elixirを少しやってみたので覚え書き
基本
-
数値:1
-
浮動小数点数:3.14
-
文字列:"hoge"
-
真理値:true or false
-
アトム(シンボル):
:atom
-
リスト:[3.14, :hoge, "fuga"]
-
タプル:{3.14, :hoge, "fuga"}
-
キーワードリスト:[foo: "bar", hoge: 300]
- キーがアトム限定の連想配列のようなもの
- キーの順序は保持される
- キーが一意かは保証されない
-
マップ:%{:foo => "bar", "hoge" => 300}
- キーは何でもいい
- 順序付けされない
- 重複キーは新しい値に置き換えられる
-
do ~ endが基本
- do:~ でワンライナー
-
関数の括弧も判別できれば省略可
-
func(a)
もfunc a
も可
-
-
特殊な記号
- _ & .. |> | -> <- ^
#IEx
iexというコマンドでインタラクティブにElixirを試せる。
そればかりかiコマンドやhコマンドでそのオブジェクト(という呼び方が正しいかは知らない)の使い方まで教えてくれる。
試しにi "hoge"
とすると"hoge"が文字列であることを教えてくれたり、Stringのヘルプを参照するといいよと教えてくれる。
なのでh String
とすると、ElixirのStringはUTF-8だよ、とかいろいろ教えてくれる。
iexちゃんマジ天使。
なのでとりあえず別ウィンドウでiexを常に走らせておく。困ったら彼女に聞こう。
Mix
mixという諸々をやってくれる装置がある。
アプリを生成したりパッケージの依存関係を解決したりライブラリをインストールしたり……
Rubyのgemやrakeがまとめて入ってるみたいな感じだろうか(イメージ)
実際にgemっぽい動きをしているのはhexというものらしいが今はいいか。
mix help
でコマンドのヘルプが出せる。
とりあえずmix new app_name
でアプリのディレクトリを作って初期化してくれること、mix run filename
でファイルを実行してくれることはわかった。
実際にnewしてみよう。アプリの名前はとりあえずboardで。
hoge>mix new board
* creating README.md
...
Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:
cd board
mix test
ファイルが出来たのでテストを走らせてみよとのこと。
テスト
board> mix test
Compiling 1 file (.ex)
Generated board app
.
Finished in 0.04 seconds
1 test, 0 failures
1つのテストが走った。テスト駆動開発もバッチリだな!
ま、テストの中身(text/board_text.exs)は1 + 1 == 2
というテストだったので通って当然だけど…。
実装
lib/board.exにいろいろ書いてみよう。まずはFizzBuzz。
defmodule Board do
@moduledoc """
Elixirの練習
"""
def fizzbuzz(n) do
cond do
rem(n, 15) == 0 -> "FizzBuzz"
rem(n, 3) == 0 -> "Fizz"
rem(n, 5) == 0 -> "Buzz"
true -> to_string(n)
end
end
def upto(max) do
for i <- 1..max do
IO.puts(fizzbuzz(i))
end
end
end
- ruby同様、doで始まりendで終わる。
- 関数はモジュールに属していないといけないらしいので、defmoduleで囲う。
-
cond
はswitch文みたいなものか。- 上から判定していき、trueになったら矢印の右側の値を返す。
- defaultに相当するものとして、一番下にtrueを書く。
- for文があり、C#のforeachに似ている。
-
1..max
はrubyなどにもある範囲(Range)オブジェクト。1..3
なら[1,2,3]を一発で表現できる。 - 標準出力は
IO.puts
で行う。
実行
コマンドmix run -e "Board.upto(30)"
で30までのFizzBuzzを実行できる。
あるいは、iex -S mix
とすれば、iexが俺のアプリを読み込んでくれるので、iex上で
Board.upto(30)
とすれば結果が返ってくる。iexちゃん天使すぎる。
毎回コマンドを指定していられなくなったら、main.exsとか適当なスクリプトを作り、
Board.upto(30)
と保存した後mix run "main.exs"
とすれば実行される。
つまり
- mix run -e "コード"
-
iex -S mix
の中でコードを実行 - mix run "ファイルパス"
のいずれかで実行できる。
Enum
こういうリスト的なやつはEnumが便利。
Enumを使うとよりきれいに書ける。
def upto(max) do
Enum.each(1..max, fn i -> IO.puts fizzbuzz(i) end)
end
Enum.eachは列挙(enumerable)と関数(Function)を渡すことで列挙の各要素に関数を適用できる。
C#でいうListのForEachかな。
列挙はさっきの範囲やリストのような続きものの総称。
関数は今回は匿名関数を使っている。
fn 引数 -> 処理 end
で書けるぞ。
C#にも似たようなのがあるよね。
list.ForEach(i => Console.WriteLine(i));
みたいな。
ちなみにFunctionは宣言した関数にアンパサンドと引数を付けても得られるので、匿名関数でなく出力する関数を作ってやって
def print(i) do
IO.puts fizzbuzz(i)
end
def upto(max) do
Enum.each(1..max, &print(&1))
end
# あるいは
def upto(max) do
Enum.each(1..max, &print/1)
end
なんてしても動く
パイプ演算子
IO.puts(fizzbuzz(i))
みたいな結果を次の関数の引数にするようなモノはパイプでつなげよう。
|>
という演算子を使うと、左辺を右辺の関数の第1引数にできる。
IO.puts(fizzbuzz(i))
はi |> fizzbuzz |> IO.puts
となるし、
Enum.each(1..10, &IO.puts(&1))
は1..10 |> Enum.each(&IO.puts(&1))
になる。
で、さっきのfizzbuzzをパイプで表現すると、
def upto(max) do
1..max |> Enum.each(fn i -> i |> fizzbuzz |> IO.puts end)
end
となる。おー、すごい……気がする。
「1~max」を「Enum.eachして」、「fizzbuzzして」、「IO.putsで出力」と処理の流れがわかりやすくなった。
パターンマッチ
関数型と言えばパターンマッチ。
elixirでは「=」がそれに当たる。
=は左辺と右辺の構造と値を比較して、当てはまるようなら左辺の変数を束縛(≒代入)する。
当てはまらなければMatchErrorが出る。
たとえばa = 1
なら、左辺は変数(なにかしらの値)、右辺は数値の1なので何事もなくaに1が束縛される。
{1, a} = {1, 2}
なら、左辺は「1と何かしらの値」、右辺は「1と2」なので滞りなくaに2が束縛される。
{2, a} = {3, 2}
だと、左辺が「2と何かしらの値」となっているのに右辺が「1と2」になっていて、左辺の2と右辺の3がマッチしないのでエラーになる。
あと、変数としてマッチしたいけど値として残したくはないときは_
という特殊な変数を使う。
_ = 1
とするとマッチはするが _
を後から参照できない。
つってもリストの総数わかんねーからマッチできねー!ってときのために|
がある。パイプで区切るとリストの途中をスキップできる。
[a, b | t] = [1,2,3,4,5]
とすればaに1、bに2、tに[3,4,5]
が束縛される。
左辺に比較したい変数を置いたのに束縛されちまうよ!っていうときは^
(ピン)演算子を使う。
束縛を防いでくれるぞ。
# MatchError
x = 1
^x = 2
case
パターンマッチを使って条件分岐するのがcaseだ。
最初にマッチした値を返してくれる。
caseでFizzBuzzを書き直してみた。
def fizzbuzz(n) do
case {rem(n, 3), rem(n, 5)} do
{0, 0} -> "FizzBuzz"
{0, _} -> "Fizz"
{_, 0} -> "Buzz"
_ -> to_string(n)
end
end
3で割った余りと5で割った余りのペアをcaseにかけ、「両方0なら」「3の余りが0なら」「5の余りが0なら」「それ以外なら」とパターンに分ける。
割り算1回分計算を減らせたね。
ガード
ところでfizzbuzz("a")といった風にすると当然エラーが出る。
数値じゃないのに剰余とかはできないからだ。
でも動的型付けでどうやって型を判定しよう?
そこで使うのがガード。
def 関数 when 条件 do
と宣言すれば関数を条件を満たしたときだけ実行できる。
def fizzbuzz(n) when n |> is_integer do
とすればnが整数のときだけ実行される。
参考文献
-
Elixir School
- とりあえずココ読んどけば間違いない
-
Elixir入門
- スライド。わかりやすい。
- Elixirの入門の独り言 - Qiita
- ExUnit.CaptureIO
- Phoenix入門:10分で作るブログ - Qiita