『Programming Elixir: Functional, Concurrent, Pragmatic, Fun』を読んでいます。
Elixirは ErlangVM (BEAM) 上で動作する関数型言語である。ErlangはEricssonで開発された言語で, WhatsApp, LINEなどで採用された実績があり, 特にネットワークサーバを書くのに向いている言語である。
Elixirのインストールから基本文法の理解を目指す。今回はI-5章まで。
本投稿における間違いは私の理解不足に依るものなので, 本書の内容の正確性とは一切関係ありません。
インストール
OS X では brewでインストールできる。Elixir 1.0.2が入った。
$ brew install elixir
iexでREPLが起動する。
2. Pattern Matching
=は代入でなく, match演算子である。変数は, = の左側にあるときだけ束縛できる。
下記ではまず, a = 1 で 1 を aに束縛することでmatch を trueにしている。
aは既に1なので, 1 = a は matchに成功する。3行目は 2 = 1 となりmatchに失敗する。
iex(1)> a = 1
1
iex(2)> 1 = a
1
iex(3)> 2 = a
** (MatchError) no match of right hand side value: 1
listにおけるmatchの例。
iex(1)> list = [1,2,3]
[1, 2, 3]
iex(2)> [a,b,c] = list
[1, 2, 3]
iex(3)> a
1
iex(4)> b
2
iex(5)> c
3
[3, 4, 5]は c に一致している。
iex(7)> list = [1,2,[3,4,5]]
[1, 2, [3, 4, 5]]
iex(8)> [a,b,c] = list
[1, 2, [3, 4, 5]]
iex(9)> c
[3, 4, 5]
再束縛を防止するには, 変数の束縛時にピン演算子 ^ をつける。
iex(1)> a = 1
1
iex(2)> a = 2
2
iex(3)> ^a = 1
** (MatchError) no match of right hand side value: 1
_を使うと破棄できる。_は参照することができない。
iex(7)> [a,_] = [1,2]
[1, 2]
iex(8)> a
1
iex(9)> _
** (CompileError) iex:9: unbound variable _
3. Immutability
例えば [1,2,3]の listに対して, それぞれの要素に100を加算したい場合, Elixirではオリジナルから新しいlistを生成する。オリジナルのlistは不変のまま。
リストの操作について。
iex(3)> list = [1,2,3]
[1, 2, 3]
iex(4)> [head | tail] = list
[1, 2, 3]
iex(5)> head
1
iex(6)> tail
[2, 3]
[ head | tail ]演算子は 1つ目の要素を head, 残りが tailとなる。
4 と list1から新しいlistを作成する。
iex(17)> list1 = [3,2,1]
[3, 2, 1]
iex(18)> list2 = [4 | list1]
[4, 3, 2, 1]
Immutable Dataの例, name.capitalizeでなく, String.capitalize name のように書く。(データ変換)
iex(19)> name = "elixir"
"elixir"
iex(20)> cap_name = String.capitalize name
"Elixir"
iex(21)> name
"elixir"
4. Elixir Basics
ElixirではBuilt-in Typesは Value, System, Collectionの3つに分類される。
まず, Valueは下記5つの型。
- Arbitrary-sized integers
- Floating-point numbers
- Atoms
- Ranges
- Regular expressions
まずはintegers, 10進数, 16進数, 8進数, 2進数。
iex(1)> 1234
1234
iex(2)> 0xcafe
51966
iex(3)> 0x765
1893
iex(4)> 0b1010
10
iex(5)>
Floating-Point NumbersはIEEE 754 に準拠している。
iex(5)> 0.1
0.1
iex(6)> 0.314159e1
3.14159
iex(7)> 314159.0e-5
3.14159
Atomは何らかの名前を表す定数である。
iex(10)> :is_binary?
:is_binary?
iex(11)> :"hey"
:hey
Rangeは, start..end のように書く。
iex(15)> Enum.map(1..10, fn x -> x * x end)
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
正規表現。match, reaplce関数など, elixir-lang.orgを参考にさせて頂いた。
iex(16)> Regex.match?(~r/foo/, "foo")
true
iex(17)> Regex.match?(~r/foo/, "bar")
false
iex(18)> Regex.replace(~r/d/, "abc", "d")
"abc"
iex(19)> Regex.replace(~r/b/, "abc", "d")
"adc"
iex(20)> Regex.run(~r/c(d)/, "abcd")
["cd", "d"]
iex(21)> Regex.run(~r/e/, "abcd")
nil
System型はErlangVM関連の型。
- PIDs and ports
- References
PIDs and ports はローカルまたはリモートプロセスのportへの参照。
また, make_ref関数はグローバルでユニークな参照を作る。
Collectionは下記4種類, 全ての型の値を保持できる。
- Tuples
- Lists
- Maps
- Binaries
Tupleはパターンマッチの中でも使える。
iex(1)> {1,2,3}
{1, 2, 3}
iex(2)> {status,count,action} = {:ok,42,"next"}
{:ok, 42, "next"}
iex(3)> status
:ok
iex(4)> count
42
iex(5)> action
"next"
Listは, ++でconcat, –でdiffernceといった演算が使える。
iex(6)> [1,2,3] ++ [4,5,6]
[1, 2, 3, 4, 5, 6]
iex(7)> [1,2,3] -- [1,2]
[3]
iex(8)> 4 in [1,2,3]
false
Mapはkey/valueのpairで, Mapのリテラルは %{ key => value, key => value } になる。
Mapへのアクセスは[]だけでなく, dot記法も使える。
iex(14)> colors = %{red: 0xffffff, green: 0xfffff0, blue: 0xffff00}
%{blue: 16776960, green: 16777200, red: 16777215}
iex(15)> colors[:red]
16777215
iex(16)> colors[:green]
16777200
iex(17)> colors[:blue]
16776960
ex(18)> colors.red
16777215
同値演算子 (===)や等値演算子 (==) など。
iex(1)> a = 1
1
iex(2)> b = 1.0
1.0
iex(3)> a === b
false
iex(4)> a !== b
true
iex(5)> a == b
true
iex(6)> a != b
false
5. Anonymous Functions
匿名関数は fnで宣言する。
iex(1)> sum = fn (a, b) -> a + b end
#Function<12.90072148/2 in :erl_eval.expr/5>
iex(2)> sum.(1,2)
3
iex(3)> greet = fn -> IO.puts "Hello" end
#Function<20.90072148/0 in :erl_eval.expr/5>
iex(4)> greet.()
Hello
:ok
iex(5)> swap = fn {a, b} -> {b, a} end
#Function<6.90072148/1 in :erl_eval.expr/5>
iex(6)> swap.({10, 5})
{5, 10}
dot-indicatesによって関数が呼ばれ, ()内が引数で渡される。
File.openはtupleを返す。成功時は1つ目の要素に :okが入り 2つ目の要素 fileに対して IO.readで初めの1行を読み込む, 失敗時には tupleに :ok以外の全てにmatchする_と 簡易なerrorが入る。
handle_open = fn
{:ok, file} -> "Read data: #{IO.read(file, :line)}"
{_, error} -> "Error: #{:file.format_error(error)}"
end
IO.puts handle_open.(File.open("tmp"))
IO.puts handle_open.(File.open("nonexistent"))
$ ls
handle_open.exs tmp
$ elixir handle_open.exs
Read data: abc
Error: no such file or directory