内包表記

Elixir には Haskell や Python のようにリスト内包表記がある。

 cf. 18 内包表記 – Comprehensions – Elixir

iex(1)> for n <- [1, 2, 3, 4], do: n * n
[1, 4, 9, 16]

生成器とフィルタ

上の式で n <- [1, 2, 3, 4] が生成器だ。列挙可能なものなら何でも生成器の右の式に入れられる。

iex(2)> for n <- 1..4, do: n * n
[1, 4, 9, 16]

生成器はパターンマッチングに対応していて、パターンにマッチしない値は無視する。次の例では、キーワードリストからキーが :good の値だけを生成している。

iex(3)> values = [good: 1, good: 2, bad: 3, good: 4]
[good: 1, good: 2, bad: 3, good: 4]
iex(4)> for {:good, n} <- values, do: n * n
[1, 4, 16]

生成器に続けてフィルタを書くと、フィルタを通った値だけが残る。次の例は奇数だけがフィルタを通り、2乗を生成する。

iex(5)> require Integer
Integer
iex(6)> for n <- 1..4, Integer.is_odd(n), do: n * n
[1, 9]

Bitstring生成器

ビットストリングを生成することもできる。

iex(7)> pixels = <<213, 45, 132, 64, 76, 32, 76, 0, 0, 234, 32, 15>>
<<213, 45, 132, 64, 76, 32, 76, 0, 0, 234, 32, 15>>
iex(8)> for <<r::8, g::8, b::8 <- pixels>>, do: {r, g, b}
[{213, 45, 132}, {64, 76, 32}, {76, 0, 0}, {234, 32, 15}]

into

通常、内包表記はリストを返す。けれど :into オプションを使うことで、結果を別の型に挿入することができる。次の例は、Bitstring 生成器と :into オプションを使って、文字列から空白文字を取り除いている。

iex(9)> for <<c <- " hello world ">>, c != ?\s, into: "", do: <<c>>
"helloworld"

try, catch, rescue

 cf. 17 トライ,キャッチとレスキュー – try, catch and rescue – Elixir

エラー

エラーの例は、例えばアトムに数を足そうとするとみることができる。

iex(1)> :atom + 1
** (ArithmeticError) bad argument in arithmetic expression
    :erlang.+(:atom, 1)

意図的にランタイムエラーを起こそうというときには、raise/1 マクロを使う。

iex(1)> raise "oops"
** (RuntimeError) oops

その他のエラーは raise/2 マクロにエラーの名前とキーワード引数を渡す。

iex(1)> raise ArgumentError, message: "Invalid argument foo"
** (ArgumentError) Invalid argument foo

モジュールの中で defexception/1 マクロを使うと、自分でエラーを定義することもできる。

iex(1)> defmodule MyError do
...(1)>   defexception message: "default message"
...(1)> end
{:module, MyError,
 <<70, 79, 82, 49, 0, 0, 14, 252, 66, 69, 65, 77, 69, 120, 68, 99, 0, 0, 1, 40,
   131, 104, 2, 100, 0, 14, 101, 108, 105, 120, 105, 114, 95, 100, 111, 99, 115,
   95, 118, 49, 108, 0, 0, 0, 4, 104, 2, ...>>, :ok}
iex(2)> raise MyError
** (MyError) default message

iex(2)> raise MyError, message: "custom message"
** (MyError) custom message

try / rescue

エラー(例外)は、try / rescue 構造で拾うことができる。

iex(2)> try do
...(2)>   raise "oops"
...(2)> rescue
...(2)>   e in RuntimeError -> e
...(2)> end
%RuntimeError{message: "oops"}

上の例ではランタイムエラーを拾って、iex で表示するようにエラー自身を返している。

とはいえ、実際には try / rescue 構造を使うことはめったにないそうだ。というのも、例えば File.read/1 関数はファイルを開くとき、失敗したらエラーを起こすのではなく、「成功した」/「失敗した」という情報を含んだタプルを返すからだ。

iex(3)> File.read "hello"
{:error, :enoent}
iex(4)> File.write "hello", "world"
:ok
iex(5)> File.read "hello"
{:ok, "world"}

ファイルを開くときに起こりうる複数の結果(成功/失敗)に対応するには、単に case でパターンマッチングすればいい。

throw / catch

Elixir では後で値を捕れるように trhow / catch 構造が用意されている。たとえば、Enum モジュールで 13 の倍数となるような最初の値を見つけたいとしよう。

iex(6)> try do
...(6)>   Enum.each -50..50, fn(x) ->
...(6)>     if rem(x, 13) == 0, do: throw(x)
...(6)>   end
...(6)>   "Got nothing"
...(6)> catch
...(6)>   x -> "Got #{x}"
...(6)> end
"Got -39"

exit

Elixir ではすべてのコードは複数のプロセスの中で動いていてたがいにメッセージをやり取りしている。プロセスが死んだとき、exit という信号を送る。あるいは exit 信号を送ることで明示的に死ぬこともできる。

iex(7)> spawn_link fn -> exit(1) end
** (EXIT from #PID<0.80.0>) 1

Interactive Elixir (1.3.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>

exittry / catch 構造でキャッチできる。

iex(1)> try do
...(1)>   exit "I am exiting"
...(1)> catch
...(1)>   :exit, _ -> "not really"
...(1)> end
"not really"

try / catch と使うことも珍しいけど、exit をキャッチするのはもっと珍しいと書いてある。
というのも、プロセスは通常、単にプロセスの exit 信号を待つだけの監視プロセスを備えた監視ツリーにくみこまれて動作していて、監視プロセスが exit 信号を受けとると、監視対象のプロセスは再起動させられるからだ。この監視システムが、エラーの後に「早く失敗する」ことでアプリケーションを既知の初期状態へ戻すことを保証している。らしい。

after

特定の動作の後、リソースをきれいに片付けられることを保証しなければならない場合、try / after が利用できる例えばファイルを開くとき、必ず閉じることを try / after で保証できる。

iex(2)> {:ok, file} = File.open "sample", [:utf8, :write]
{:ok, #PID<0.120.0>}
iex(3)> try do
...(3)>   IO.write file, "ola"
...(3)>   raise "oops, something went wrong"
...(3)> after
...(3)>   File.close(file)
...(3)> end
** (RuntimeError) oops, something went wrong