5 case,condそしてif - case, cond and if

この章ではcasecondそしてifといった処理制御構造について学んでいきます.

In this chapter, we will learn about the case, cond and if control-flow structures.

5.1 case - case

caseによってある値がいずれか一つにマッチするまで複数のパターンと比較することができるようになります:

case allows us to compare a value against many patterns until we find a matching one:

iex> case {1, 2, 3} do
...>   {4, 5, 6} ->
...>     "This clause won't match"
...>   {1, x, 3} ->
...>     "This clause will match and bind x to 2 in this clause"
...>   _ ->
...>     "This clause would match any value"
...> end

もし既にある変数とパターンマッチさせたい場合,^を使わなければなりません:

If you want to pattern match against an existing variable, you need to use the ^ operator:

iex> x = 1
1
iex> case 10 do
...>   ^x -> "Won't match"
...>   _  -> "Will match"
...> end

句では,ガード(guard)を指定することで条件を拡張することができます:

Clauses also allow extra conditions to be specified via guards:

iex> case {1, 2, 3} do
...>   {1, x, 3} when x > 0 ->
...>     "Will match"
...>   _ ->
...>     "Won't match"
...> end

最初の句はxが正の場合にのみマッチします.

The first clause above will only match when x is positive.

5.2 ガード句の中の式 - Expressions in guard clauses.

ErlangVMではガードで使える式に制限があります:

The Erlang VM only allows a limited set of expressions in guards:

  • 比較演算子(==!====!==><<=>=)
  • 論理演算子(andor)と否定演算子(not!)
  • 算術演算子(+-*/)
  • 左側がリテラルの場合の<>++
  • 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)
    • rem(integer, integer)
    • round(number)
    • self()
    • tl(list)
    • trunc(number)
    • tuple_size(tuple)
  • comparison operators (==, !=, ===, !==, >, <, <=, >=)

  • boolean operators (and, or) and negation operators (not, !)

  • arithmetic operators (+, -, *, /)

  • <> and ++ as long as the left side is a literal

  • the in operator

  • all the following type check functions:

    • 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
  • plus these functions:

    • 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)
    • rem(integer, integer)
    • round(number)
    • self()
    • tl(list)
    • trunc(number)
    • tuple_size(tuple)

ガードの中で起きたエラーは単純にガードの失敗とみなされてガードの外には影響しません:

Keep in mind errors in guards do not leak but simply make the guard fail:

iex> hd(1)
** (ArgumentError) argument error
    :erlang.hd(1)
iex> case 1 do
...>   x when hd(x) -> "Won't match"
...>   x -> "Got: #{x}"
...> end
"Got 1"

もしどの句にもマッチしなければ,エラーが発生します:

If none of the clauses match, an error is raised:

iex> case :ok do
...>   :error -> "Won't match"
...> end
** (CaseClauseError) no case clause matching: :ok

匿名関数でも句やガードを複数持つことができることに注意してください:

Note anonymous functions can also have multiple clauses and guards:

iex> f = fn
...>   x, y when x > 0 -> x + y
...>   x, y -> x * y
...> end
#Function<12.71889879/2 in :erl_eval.expr/5>
iex> f.(1, 3)
4
iex> f.(-1, 3)
-3

匿名関数の句では引数の数が同じでなければなりません,そうでない場合はエラーになります.

The number of arguments in each anonymous function clause needs to be the same, otherwise an error is raised.

5.3 cond - cond

caseは異なる値に対して(1つの値を)マッチさせようとするときにはうまく使えます.しかし場合によっては最初に評価した式がtrueになるまで複数の条件をチェックしたい場合があります.そのような場合にcondが使えるでしょう:

case is useful when you need to match against different values. However, in many circumstances, we want to check different conditions and find the first one that evaluates to true. In such cases, one may use cond:

iex> cond do
...>   2 + 2 == 5 ->
...>     "This will not be true"
...>   2 * 2 == 3 ->
...>     "Nor this"
...>   1 + 1 == 2 ->
...>     "But this will"
...> end
"But this will"

これは命令型言語のelse if句と同じです(それらより使う機会は少ないですが).

This is equivalent to else if clauses in many imperative languages (although used way less frequently here).

もしtrueになる条件がなければ,エラーになります.そのため,条件にtrueになるものを加えて,常にマッチさせることが必要になるかもしれません:

If none of the conditions return true, an error is raised. For this reason, it may be necessary to add a final condition, equal to true, which will always match:

iex> cond do
...>   2 + 2 == 5 ->
...>     "This is never true"
...>   2 * 2 == 3 ->
...>     "Nor this"
...>   true ->
...>     "This is always true (equivalent to else)"
...> end

最後にcondではnilfalse以外はどんな値でもtrueとして扱うことに気をつけてください:

Finally, note cond considers any value besides nil and false to be true:

iex> cond do
...>   hd([1,2,3]) ->
...>     "1 is considered as true"
...> end
"1 is considered as true"

5.4 ifとunless - if and unless

casecondとは違い,一つだけ条件をチェックしたい場合に便利なif/2unless/2といったマクロも提供しています.

Besides case and cond, Elixir also provides the macros if/2 and unless/2 which are useful when you need to check for just one condition:

iex> if true do
...>   "This works!"
...> end
"This works!"
iex> unless true do
...>   "This will never be seen"
...> end
nil

もしif/2に渡した条件がfalsenilで返ってきた場合,do/endで囲まれた部分は実行されず,単にnilが返ります.その逆になるのがunless/2です.

If the condition given to if/2 returns false or nil, the body given between do/end is not executed and it simply returns nil. The opposite happens with unless/2.

これらではelseブロックも使えます:

They also support else blocks:

iex> if nil do
...>   "This won't be seen"
...> else
...>   "This will"
...> end
"This will"

メモ: この言語でif/2unless/2がマクロとして実装されているのがおもしろい点です;これらは他の様々な言語でそうであるような特別な言語構造ではありません.モジュールKernelのドキュメントif/2のドキュメントやソースを調べることができます.モジュールKernelには他にも演算子+/2や関数is_function/2といったような,あなたがコードを書くときにデフォルトで自動importされて使えるようになっているものが定義されています.

Note: An interesting note regarding if/2 and unless/2 is that they are implemented as macros in the language; they aren't special language constructs as they would be in many languages. You can check the documentation and the source of if/2 in the Kernel module docs. The Kernel module is also where operators like +/2 and functions like is_function/2 are defined, all automatically imported and available in your code by default.

5.5 doブロック - do blocks

ここまでで,私たちは4つの制御構造casecondifそしてunlessを学びました,そしてこれらは全てdo/endブロックで囲まれていました.実はifを以下のようにも書けます:

At this point, we have learned four control structures: case, cond, if and unless, and they were all wrapped in do/end blocks. It happens we could also write if as follows:

iex> if true, do: 1 + 2
3

Elixirでは,do/endブロックは式のまとまりを簡単にdo:へ渡すためのものです.これらは同じことを表しています:

In Elixir, do/end blocks are a convenience for passing a group of expressions to do:. These are equivalent:

iex> if true do
...>   a = 1 + 2
...>   a + 10
...> end
13
iex> if true, do: (
...>   a = 1 + 2
...>   a + 10
...> )
13

2番目の構文はキーワードリストを使っていると言っています.elseも同じ構文で渡せます:

We say the second syntax is using keyword lists. We can pass else using this syntax:

iex> if false, do: :this, else: :that
:that

do/endブロックを使う際には一つ気をつけることがあります.ブロックは常に一番遠くの関数呼び出しに束縛されます.例えば,以下の式:

One thing to keep in mind when using do/end blocks is they are always bound to the outermost function call. For example, the following expression:

iex> is_number if true do
...>  1 + 2
...> end

はこのように解釈されます:

Would be parsed as:

iex> is_number(if true) do
...>  1 + 2
...> end

そうするとElixirはis_number/2を呼び出そうとして未定義関数エラーになります.あいまいさを解消するのには明示的に括弧を追加すると良いです:

Which leads to an undefined function error as Elixir attempts to invoke is_number/2. Adding explicit parentheses is enough to resolve the ambiguity:

iex> is_number(if true do
...>  1 + 2
...> end)
true

キーワードリストはこの言語においてとても重要な役割を担っており,様々な関数やマクロにおいてあたりまえの存在です.後の章でこのことをもう少し詳しくみていきます.今は"バイナリ,文字列そして文字リスト"について話していきましょう.

Keyword lists play an important role in the language and are quite common in many functions and macros. We will explore them a bit more in a future chapter. Now it is time to talk about "Binaries, strings and char lists".