Elixirの演算子やパターンマッチ

Tsukasa OISHI

前回のElixirの勉強 (前回の記事 Elixirの型についてちょっと勉強) からちょっと時間が空きましたが、Elixirの勉強の続き。今日は演算子、それからいよいよパターンマッチについてです。

演算子

1.文字列の結合

文字列の結合には <> を使います。

iex(1)> "おおいし" <> "つかさ"
"おおいしつかさ"

2.論理演算子

&& , || そして ! を使います。 falsenil だけが として扱われます。rubyと同じですね。

iex(2)> 0 || 1
0
iex(3)> nil || 2
2
iex(4)> false && 3
false
iex(5)> "11" && 4
4
iex(6)> !nil
true
iex(7)> !1
false

他に、 and, or そして not があります。これらは、&&などとは振る舞いが異なります。
第1引数が必ず true または false でないといけません。

iex(21)> true and 1
1
iex(22)> false and 2
false
iex(23)> true or 3
true
iex(24)> false or 4
4
iex(25)> not true
false
iex(26)> not false
true

true, false 以外を渡すと例外となります。

iex(27)> nil and 1
** (ArgumentError) argument error: nil

3.比較演算子

==, !=, <=, >=, <, > があります。

iex(33)> 1 == 1
true
iex(34)> 1 != 1
false
iex(35)> 2 <= 3
true
iex(36) 2 >= 3
false
iex(37)> "22" < "21"
false
iex(38)> "22" < "22"
false
iex(39)> "22" < "23"
true

===, !== もあります。これらは整数と浮動小数点をちゃんと区別します。

iex(43)> 1 == 1.0
true
iex(44)> 1 === 1.0
false

異なる型同士でも比較できます。型の大小は以下のように定義されています。

number < atom < reference < functions < port < pid < tuple < maps < list < bitstring
iex(46)> 1 < :atom
true

パターンマッチ

パターンマッチはElixirにおいてとても大事な概念です。ここをちゃんと理解しておかないといけません。

1.マッチ演算子

一般的な言語では、 = は変数への代入を意味します。
Elixirには代入というものはありません。 = はマッチ演算子といい、右辺と左辺のパターンが一致しているかをチェックします。

iex(53)> 1 = 1
1
iex(54)> 1 = 2
** (MatchError) no match of right hand side value: 2

パターンが一致しない場合は例外となります。

左辺にいままで登場しなかった変数が登場すると、Elixirはパターンが一致するように変数の値を右辺のものに束縛します。

iex(55)> x = 1
1
iex(56)> 1 = x
1

束縛というのは聞き慣れない言葉かもしれません。代入しているわけではなく、パターンが一致するように変数の値を 束縛 しているというわけです。
一度束縛された変数は、マッチ演算子の右辺に登場することもできるし、左辺が変数である必要もありません。

一度も登場したことのない変数が右辺に登場すると例外になります。

iex(57)> 1 = y
** (CompileError) iex:57: undefined function y/0

y がいままで登場していないため、Elixirはそれが関数だと考えてコールしようとします。y という関数はないので、例外になってしまうというわけです。

2.ピン演算子

Erlangでは、一度束縛された変数に違う値を束縛することはできません。変数の値が変化しないという制限があることで、いろいろと恩恵があるのです。
はじめてプログラミング言語を勉強したとき、 x = x + 1 という式に違和感を覚えたことを思い出してください。これって数学ではありえない式ですよね? Earlangの変数とは値を入れる箱ではなくて、数学の変数と同じものなのです。

ところでElixirは一度束縛された変数に違う値を束縛しなおすことができます(えっ)。

iex(1)> x = 1
1
iex(2)> x = 2
2
iex(3)> x = x + 1
3

Elixirが再束縛を許している理由はまだよくわかりません。Erlangと同様に再束縛できないようにしたほうがいい気がするのですが。
Elixirでも再束縛を防ぐことができます。

iex(1)> x = 1
1
iex(2)> ^x = 2
** (MatchError) no match of right hand side value: 2

パターンマッチの際、変数の頭に ^ をつけると再束縛を禁止します。これによって単純なパターンマッチとして扱うことができるようになります。
これをデフォルトにしてほしい気がしますね。

3.パターンマッチング

パターンマッチを使うといろいろと便利なことでできます。
たとえば以下のようなパターンマッチを行うと、タプル内の各要素を個別の変数に束縛することができます。

iex(1)> {a, b, c} = {1, :ok, "おおいし"}
{1, :ok, "おおいし"}
iex(2)> a
1
iex(3)> b
:ok
iex(4)> c
"おおいし"

タプルのサイズが異なっていたり、型が違っていたりするとパターンが一致しないため例外になります。

iex(1)> {a, b, c} = {1, 2}
** (MatchError) no match of right hand side value: {1, 2}

iex(1)> {a, b, c} = 1
** (MatchError) no match of right hand side value: 1

iex(1)> {a, b, c} = [1, 2, 3]
** (MatchError) no match of right hand side value: [1, 2, 3]

一部の値だけを変数に束縛することができます。

iex(1)> {:ok, code} = {:ok, 200}
{:ok, 200}
iex(2)> code
200
iex(3)> {:ok, code2} = {:server_error, 500}
** (MatchError) no match of right hand side value: {:server_error, 500}

リストには head と tail という概念がありましたね。パターンマッチでもこれが生きてきます。

iex(1)> [x | y] = [1, 2, 3, 4]
[1, 2, 3, 4]
iex(2)> x
1
iex(3)> y
[2, 3, 4]

| の前の部分が head にあたり、後ろの部分が tail となります。

4.アンダースコア変数

変数にアンダースコアを使うと、パターンマッチはします(どんな値でもマッチします)が束縛は行いません。アンダースコア変数を参照しようとすると例外になります。使用するつもりのない値があるときに使用します。

iex(1)> _ = 1
1
iex(2)> _
** (CompileError) iex:2: unbound variable _

iex(2)> [head | _] = [1, 2, 3, 4]
[1, 2, 3, 4]
iex(3)> head
1
iex(4)> _
** (CompileError) iex:4: unbound variable _

アンダースコアに続いて名前をつけることもできますが、これも無視する値のときに使用します。ただし束縛はされるし参照もされます。参照すると警告が発生します。参照するような値はアンダースコアで始まらないようにしましょう。

iex(1)> _tmp = 1
1
iex(2)> _tmp
iex:2: warning: the underscored variable "_tmp" is used after being set. A leading underscore indicates that the value of the variable should be ignored. If this is intended please rename the variable to remove the underscore
1
iex(3)> [head | _tmp] = [1, 2, 3, 4]
[1, 2, 3, 4]
iex(4)> head
1
iex(5)> _tmp
iex:7: warning: the underscored variable "_tmp" is used after being set. A leading underscore indicates that the value of the variable should be ignored. If this is intended please rename the variable to remove the underscore
[2, 3, 4]