URL
試した環境
- Ubuntu Server 14.04 LTS
- Erlang/OTP 18
- Elixir 1.0.4
case, cond and if
case
caseでマッチするのが見つかるまで複数のパターンについて比較することができる。
iex> case { 1, 2, 3 } do
...> {4,5,6} -> "この節はマッチしない"
...> {1,x,3} -> "この節にマッチし、xは2にバインドされる"
...> _ -> "この節はどんな値でもマッチする"
...> end
"この節にマッチし、xは2にバインドされる"
この時、もし既に束縛した値とパターンマッチさせたい場合は、^
演算子を使う。
iex> x = 1
1
iex> case 10 do
...> ^x -> "マッチしない"
...> _ -> "マッチする"
...> end
"マッチする"
^x
にパターンマッチさせる場合のサンプル。
iex> case {1, 3} do
...> {^x, y} -> "マッチして、yは3にバイドされる"
...> _ -> "大抵ここでどれにもマッチしない場合の処理をする"
...> end
"マッチして、yは3にバイドされる"
節ではガードを使うことで条件を追加することができる。
iex> case {1, 2, 3} do
...> {1, x, 3} when x > 0 -> "xが0より大きい場合にマッチする"
...> _ -> "ここではマッチしない"
...> end
"xが0より大きい場合にマッチする"
iex> case {1, 2, 3} do
...> {1, x, 3} when x < 0 -> "xが0より小さい場合にマッチする"
...> _ -> "ここではマッチする"
...> end
"ここではマッチする"
Expressions in guard clauses
Erlang VMではガード中での式は限られている。
- 比較演算子
==
,!=
,===
,!==
,>
,<
,<=
,>=
- 短絡boolean演算子
and
,or
- 否定演算子
!
- 左側がリテラルの場合の
<>
,++
-
in
演算子 - 以下の型チェック関数は全てガード中で使える
is_atom/1
is_binary/1
is_bitstring/1
is_boolean/1
is_float/1
is_function/1
is_function/2
is_integer/1
is_list/1
is_map/1
is_number/1
is_pid/1
is_port/1
is_reference/1
is_tuple/1
- 次の関数もガード中で使える
abs(number)
bit_size(bitstring)
byte_size(bitstring)
div(integer,integer)
elem(tuple, n)
hd(list)
length(list)
map_size(map)
node()
node(pid | ref | port)
round(number)
self()
trunc(number)
tuple_size(number)
ガード中で起きたエラーは単にガードの失敗となり、ガードの外に影響しない。
iex> case 1 do
...> x when hd(x) -> "マッチしない"
...> x -> "#{x} を使える"
...> end
"1 を使える"
もし、どの節にもマッチしない場合は、エラーが起きる。
iex> case :ok do
...> :error -> "マッチしない"
...> :ng -> "これもマッチしない"
...> end
** (CaseClauseError) no case clause matching: :ok
無名関数も節やガードを持つことができる。
iex> f = fn
...> x, y when x > 0 -> x + y
...> x, y -> x * y
...> end
#Function<12.54118792/2 in :erl_eval.expr/5>
iex> f.(1, 3)
4
iex> f.(-1, 3)
-3
無名関数の各節の引数は同じでなければならない。それ以外の場合はエラーが起きる。
iex> f = fn
...> x, y when x > 0 -> x + y
...> x, y, z -> x + y + z
...> end
** (CompileError) iex:12: cannot mix clauses with different arities in function definition
(elixir) src/elixir_translator.erl:17: :elixir_translator.translate/2
cond
case
は異なる値に対して、1つの値をマッチせる場合に役に立つ。
しかし、多くの状況で、様々な条件をチェックしてtrue
に評価される最初のものを見つけたいことがある。このような場合にcond
が使える。
iex> cond do
...> 2 + 2 == 5 -> "trueにならない"
...> 2 * 2 == 3 -> "これもtrueにならない"
...> 1 + 1 == 2 -> "これはtrueになる"
...> end
"これはtrueになる"
これは多くの命令型言語のelse if
文と同じ(利用する頻度は少ないが)。
もし、trueになる条件がなければエラーになる。
iex> cond do
...> 2 * 2 == 3 -> "これもtrueにならない"
...> 2 * 2 == 3 -> "これもtrueにならない"
...> end
** (CondClauseError) no cond clause evaluated to a true value
そのため、最後にtrue
を加えることで対応する必要がある場合がある。
iex> cond do
...> 2 + 2 == 5 -> "これはtrueにならない"
...> 2 * 2 == 3 -> "これもtrueにならない"
...> true -> "常にtrueになる"
...> end
"常にtrueになる"
最後に、cond
ではnil
とfalse
以外はどんな値でもtrue
とされることを気をつける
iex> cond do
...> hd([1,2,3]) -> "1はtrueとされる"
...> end
"1はtrueとされる
iex> cond do
...> hd([:a, :b, :c]) -> "atomもtrue"
...> end
"atomもtrue"
下記は、aが未定義の関数として処理されるためエラーになる。
iex> cond do
...> hd([a,b,c]) -> "これはエラー"
...> end
** (RuntimeError) undefined function: a/0
if and unless
case
、 cond
の他に、Elixirは1つの条件をチェックする必要がある場合に役に立つif/2
と unless/2
のマクロを提供している。
iex> if 2 == 1 + 1 do
...> "これは通る"
...> end
"これは通る"
iex> if 2 == 1 - 1 do
...> "これは通らない"
...> end
nil
if/2
に与えた条件が false
か nil
を返す場合は、do
と end
で囲まれた部分は実行されず、単にnil
が返却される。 if/2
に与えた条件がfalseかnil以外の場合は、do
と end
で囲まれた部分が実行される。
false
か nil
以外なので、例えば、与えた条件がatomを返しても do
と end
の部分は実行される
iex> if :atom do
...> "これは通る"
...> end
"これは通る"
反対に、unless/2
に与えた条件がfalse
かnil
の場合、do
とend
で囲まれた部分は実行され、それ以外の場合はnil
を返す。
iex> unless false do
...> "これは通る"
...> end
"これは通る"
iex> unless true do
...> "これは通らない"
...> end
nil
iex> unless 2 == 1 + 1 do
...> "これは通らない"
...> end
nil
iex> unless 2 == 1 - 1 do
...> "これは通る"
...> end
"これは通る"
iex> unless :atom do
...> "これは通らない"
...> end
nil
if/2
と unless/2
は、else
ブロックが使える。
iex> if nil do
...> "これは通らない"
...> else
...> "これを通す"
...> end
"これを通す"
この言語で、
if/2
やunldess/2
がマクロとして実装されていることは興味深いらしい。kernelモジュールのドキュメントで、もう少し詳しい内容を調べられるようなので、気力があるひとは見ると良さそう。kernelモジュールは、+/2
演算子やis_function/2
のような関数が定義されているとのこと。
do blocks
ここまでで、case
, cond
, if
, unless
を学んだが、全てdo
とend
のブロックで囲まれていた。
実はif
を次のようにも書ける
iex> if true, do: 1 + 2
3
do
と end
のブロッグは、式のまとまりを便利にdo:
へ渡すもの。
下記のものは等価。
iex> if true do
...> a = 1 + 2
...> a + 10
...> end
13
iex> if true, do: (
...> a = 1 + 2
...> a + 10
...> )
13
私たちは2つ目の構文をキーワードリストを使っていると呼んでいる。 この構文を使ってelse
も渡すことができる。
iex> if false, do: 2 + 2, else: 1 + 1
2
do
と end
ブロックを使う際には心に留めておくことがある。ブロックは常に一番外側の関数呼び出しに束縛されるということ。
例えば、次の式。
iex> if_number if true do
...> 1 + 2
...> end
次のように解釈される。if
が引数1つしか与えられないので、失敗する様子。
GETTING STARTEDでは、is_number/2
を呼びだそうとして、未定義関数エラーになるとあるが、実際のエラーの内容は、先にif
の処理がされているのか、ifが未定義関数としてエラーになっている。
iex> is_number(if true) do
...> 1 + 2
...> end
** (RuntimeError) undefined function: if/1
明示的に、括弧を追加してやれば、解決する。
iex> is_number(if true do
...> 1 + 2
...> end )
true
キーワードリストはElixirにおいて、とても重要な役割をになっており、多くの関数やマクロで非常に一般的とのこと。