LoginSignup
8
8

More than 5 years have passed since last update.

ElixirのGETTING STARTED(5.case, cond and if)をやってみた

Last updated at Posted at 2015-07-08

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ではnilfalse以外はどんな値でも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

casecond の他に、Elixirは1つの条件をチェックする必要がある場合に役に立つif/2unless/2 のマクロを提供している。

iex> if 2 == 1 + 1 do 
...>  "これは通る"     
...> end
"これは通る"
iex> if 2 == 1 - 1 do 
...> "これは通らない"
...> end
nil

if/2 に与えた条件が falsenil を返す場合は、doend で囲まれた部分は実行されず、単にnilが返却される。 if/2 に与えた条件がfalseかnil以外の場合は、doend で囲まれた部分が実行される。
falsenil 以外なので、例えば、与えた条件がatomを返しても doend の部分は実行される

iex> if :atom do                                         
...> "これは通る"
...> end
"これは通る"

反対に、unless/2に与えた条件がfalsenilの場合、doendで囲まれた部分は実行され、それ以外の場合は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/2unless/2 は、else ブロックが使える。

iex> if nil do 
...> "これは通らない"
...> else
...> "これを通す"
...> end
"これを通す"

この言語で、if/2unldess/2がマクロとして実装されていることは興味深いらしい。kernelモジュールのドキュメントで、もう少し詳しい内容を調べられるようなので、気力があるひとは見ると良さそう。kernelモジュールは、+/2演算子やis_function/2のような関数が定義されているとのこと。

do blocks

ここまでで、case, cond , if , unlessを学んだが、全てdoendのブロックで囲まれていた。
実はifを次のようにも書ける

iex> if true, do: 1 + 2
3

doendのブロッグは、式のまとまりを便利に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

doend ブロックを使う際には心に留めておくことがある。ブロックは常に一番外側の関数呼び出しに束縛されるということ。
例えば、次の式。

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において、とても重要な役割をになっており、多くの関数やマクロで非常に一般的とのこと。

8
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
8