【関数型言語】Elixir の基本文法 (1)

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