yu-tarrrrの日記

完全に個人の趣味でその時々書きたいことを書く

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
  • 真理値の truefalse はそれぞれアトムの:true:falseである
iex(13)> (:hoge == :fuga) == :false
true
iex(14)> (:hoge == :fuga) == false
true

文字列

  • UTF-8エンコードされているので、""で囲む
  • 変数展開みたいなことや文字列結合はできる
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"]
  • 減算の場合、存在しない値を渡しても安全である
  • また、リスト内に重複した値を持つとき、減算される値は最初に出てきた値である(線形操作になるので)
  • hdtlで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

  • condelse 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