Elixir School 1日目
はじめに
- 今回から早速Elixirの勉強を始める
- 完全に自分のためのアウトプットである
- しばらくはiexをベースに基礎編のインプット・アウトプットをし、それが終わり次第umbrellaプロジェクトを作成して進めていく
基本データ型について
- 基本データ型で用意しているものは、
整数
,浮動小数
,真理値
,アトム
,文字列
整数
- 組み込みで2進数、8進数、16進数にに対応してる
iex(1)> 11 11 iex(2)> 0b11 3 iex(3)> 0o11 9 iex(4)> 0x11 17
アトム
- 自身の名前が値になる定数のこと
iex(11)> :hoge :hoge iex(12)> :hoge == :fuga false
- 真理値の
true
とfalse
はそれぞれアトムの:true
と:false
である
iex(13)> (:hoge == :fuga) == :false true iex(14)> (:hoge == :fuga) == false true
文字列
iex(17)> name = "tom" "tom" iex(18)> name "tom" iex(19)> "hello #{name}" "hello tom" iex(20)> "hello #{name <> " tom"}" "hello tom tom"
比較・計算
- 基本的な計算は四則演算は他の言語と同じ
- 基本的な等号・不等号も使える
コレクション
- リスト、タプル、キーワードリスト、マップについて
リスト
- リストは値の単純なコレクションなので、異なるデータ型を1つのリスト内に持つことは可能
- Elixirのリストは連結リストとして実装されている。つまり、リストの長さを得るのには線形時間の操作になる。なので、リストに値を追加する場合は先頭に追加した方が、末尾に追加するよりも高速である場合がほとんどである。
iex(26)> list = [1, "2"] [1, "2"] ## 末尾に追加する(低速) iex(33)> list= list ++ ["7"] [1, "2", "7"] iex(34)> list [1, "2", "7"] ## 先頭に追加する(高速) iex(35)> list = [3.14 | list] [3.14, 1, "2", "7"] iex(36)> list [3.14, 1, "2", "7"]
- リスト同士の結合・減算も可能
- リストの連結には
++/2
、減算には--/2
演算子を用いる - 上記の名前(++/2)の形式について: Elixir(とその土台のErlang)において、関数や演算子の名前は2つの部分、与えられた名前(ここでは ++)とその アリティ から成る。アリティはElixir(とErlang)のコードについて説明するときの中核となるもの。アリティは関数や演算子が取る引数の数(この場合は2)。名前とアリティはスラッシュで繋げられる。
iex(37)> list [3.14, 1, "2", "7"] iex(38)> list2 = [1, 2] [1, 2] iex(39)> list ++ list2 [3.14, 1, "2", "7", 1, 2] iex(40)> list -- list2 [3.14, "2", "7"]
- 減算の場合、存在しない値を渡しても安全である
- また、リスト内に重複した値を持つとき、減算される値は最初に出てきた値である(線形操作になるので)
hd
とtl
で1つ目の要素と残りの要素にアクセスすることができる
タプル
- タプルはリストに似ているが、各要素はメモリ上に隣接して格納されるので、タプルの長さを得るのは高速であるが、修正を行うのは高コストとなる。
iex(42)> {"hoge", 1 , :fuga} {"hoge", 1, :fuga}
キーワードリスト
- キーワードリストとマップはElixirの連想コレクション。Elixirでは、キーワードリストは最初の要素がアトムのタプルからなる特別なリストで、リストと同様の性能になる
- キーの順序は担保されるが、一意性は担保されない
iex(45)> [foo: "bar", hello: "world"] [foo: "bar", hello: "world"] iex(46)> [foo: "bar", hello: "world", foo: "hello"] [foo: "bar", hello: "world", foo: "hello"]
マップ
- キーバリューストア。 キーワードリストとは違いどんな型のキーも使え、順序付けはされない。 マップは %{} 構文で定義することができる
- キーの一意性は担保されて、値が上書きされる(存在しないキーにアクセスした場合はエラーが返る)
Enum
- Enumモジュールには70以上の関数がある
- 公式 を参照すれば全部見れるが必要になったタイミングで適宜調べる予定
- 因みに
iex
からも参照は可能。Enum.infoを叩けばキーワードリストが返却される
iex(2)> Enum.__info__(:functions) [...]
パターンマッチング
- パターンマッチングとは 参考
データを検索する場合に、特定のパターンが出現するかどうか、またどこに出現するかを特定する手法のことである
- Elixirではパターンマッチングは強力な部品で、簡単な値やデータ構造に始まり、関数までマッチすることができる
- まずは、マッチ演算子とピン演算子を簡単に使ってみる
マッチ演算子
iex(1)> a = 1 1 iex(2)> 1 = a 1 iex(3)> 2 = a ** (MatchError) no match of right hand side value: 1
- Elixirでは、
=
演算子は実際には代数学での等号に値するマッチ演算子。このマッチ演算子を通して値を代入し、その後マッチさせることができる。マッチングに成功すると方程式の結果を返し、失敗する場合はエラーを投げる。
ピン演算子
iex(6)> b = 1 1 ## ここでマッチ演算子を使うと代入される iex(7)> b = 2 2 iex(8)> b 2 iex(9)> b = 1 1 ## ピン演算子を使うことで、bを束縛することを防げる iex(10)> ^b = 2 ** (MatchError) no match of right hand side value: 2 iex(10)> b 1
制御構造
if
,unless
,case
,cond
について
ifとunlessについて
- 使い方は基本的に他の言語と同じ
- 条件に合致したら、分岐の中にはいり入らなければ
else
に入って処理が行われる - Elixirでは偽とみなされる値は nil と真理値の false だけ
iex> if String.valid?("Hello") do ...> "Valid string!" ...> else ...> "Invalid string." ...> end "Valid string!" iex> unless String.valid?("Hello") do ...> "Valid string!" ...> else ...> "Invalid string." ...> end "Invalid string!" ## elseが定義されずいずれの条件にも合致しない場合はnilが返る iex> if String.valid?(1) do ...> "hoge" ...> end nil
case
- 複数のパターンにマッチさせたいときは
case
を使う _
で当てはまらなかった時の挙動をハンドルしないとエラーが発生するcase/2
はパターンマッチングに依存しているため、パターンマッチングと同じルールや制限が全て適用される。既存の変数に対してマッチさせようという場合にはピン ^ 演算子を使わないと、変数は再度拘束される- ガード説にも対応している
iex(17)> a 1 iex(18)> case 2 do ...(18)> ^a -> "match" ...(18)> _ -> "not match" ...(18)> end "not match" iex(19)> b = [1, 2] [1, 2] iex(20)> case [1, 3] do ...(20)> [1, x] when x < 2 -> "hoge" ...(20)> _ -> "fuga" ...(20)> end "fuga"
cond
cond
はelse if
とほとんど同じ- 条件に当てはまらないときに、エラーを返すので
true
になるような条件を定義しておく
iex(1)> cond do ...(1)> 1 + 2 == 4 -> "hoge" ...(1)> true -> "fuga" ...(1)> end "fuga"
関数
匿名関数
- 関数名を持たない関数
fn
,end
,->
で構成される
## 通常の書き方 fn (a, b) -> a + b end ## 省略記法 &(&1 + &2)
パターンマッチング
- 変数に限らず、関数にパターンマッチングも適用することが可能
iex> handle_result = fn ...> {:ok, result} -> IO.puts "Handling result..." ...> {:ok, _} -> IO.puts "This would be never run as previous will be matched beforehand." ...> {:error} -> IO.puts "An error has occurred!" ...> end iex> some_result = 1 iex> handle_result.({:ok, some_result}) Handling result... iex> handle_result.({:error}) An error has occurred!
名前付き関数
- モジュールを定義し、その中で関数を命名しておくことが可能
- そうすることで、他のモジュールからも呼び出すことが可能になる
- 関数名は同じでもアリティが異なれば別の関数として定義される
defmodule Greeter do def hello(name) do "Hello, " <> name end end ## 1行ですむなら `do:`でもかける defmodule Greeter do def hello(name), do: "Hello, " <> name end
private関数
- 同一モジュールの中からしか呼び出せない関数
- 他の言語とも同じ
defp
で定義する
defmodule Greeter do def hello(name), do: phrase() <> name defp phrase, do: "Hello, " end
ガード
when
で条件を定義して関数の呼び出しを制御する
defmodule Greeter do def hello(names) when is_list(names) do names |> Enum.join(", ") |> hello end def hello(name) when is_binary(name) do phrase() <> name end defp phrase, do: "Hello, " end