ヽ(´・肉・`)ノログ

How do we fighting without fighting?

elixirのgetting-startedやってみる(2)

つづき. 0.10.3 の getting-started を元に書いている. 適当に自分が理解できる程度に訳しているので全く信用してはいけない.

2 Diving in

基本的な型についてもうちょっと詳しくつっこんでみる. あとは制御フローと無名関数について話す.

2.1 リストとタプル

iex> is_list [1,2,3]
true
iex> is_tuple {1,2,3}
true

どうやってメモリに保持されるかが違う.

リストは linked list で実装されている. つまり,ある要素を調べると,次の要素への参照先が書いてある.

対して,タプルは連続したメモリを確保して保持されている.

これはつまり,タプルは要素へのアクセスが速い. しかし,タプルのあるデータを更新するときは,それ以外の要素のメモリを全てコピーするので高くつく.

タプルの要素は elem と set_elem で取得/更新できる

iex > elem { :a, :b, :c }, 0
:a
iex > set_elem { :a, :b, :c }, 0 :d
{:d, :b, :c}

タプルの要素の更新は遅いので,要素を追加/削除するときはリストを使う. リストはリンクされている.つまり,最初の要素にアクセスするのは手数が少ない. N 番目の要素にアクセスするには N-1 個のノードを通らなければならない.

リストの先頭は以下のように取得できる

# 先頭と,リストの残りに合致させる
iex> [head | tail] = [1,2,3]
[1,2,3]
iex> head
1
iex> tail
[2,3]

# 先頭と,リストの残りを元に戻す
iex> [head | tail]
[1,2,3]
iex> length [head | tail]
3

上の例のように先頭の値は head ,残りのリストは tail で取得できている. これは パターンマッチング と呼ばれている.

タプルでもパターンマッチングできる

iex> { a, b, c } = { :hello, "world", 42 }
{ :hello, "world", 42 }
iex> a
:hello
iex> b
"world"

パターンマッチングは両辺が揃わないとエラーになる.

例えば,異なるサイズのタプルをパターンマッチさせたとき

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

異なる型をパターンマッチさせたとき

iex> { a, b, c } = [:hello, "world", '!']
** (MatchError) no match of right hand side value: [:hello,"world",'!']

最も興味深いのは,特定の値にマッチさせることができる. 次の例は,右側がタプルで,キーとして :ok という atom から始まるものだけを受けつけるということを,左側の部分で指定している.

iex> { :ok, result } = { :ok, 13 }
{:ok,13}
iex> result
13

iex> { :ok, result } = { :error, :oops }
** (MatchError) no match of right hand side value: {:error,:oops}

パターンマッチングを使うと,データ型,例えばリストやタプルを簡単に解体できるようになる. これは Elixir で再帰を扱うときの重要な要素のうちの一つとなる.後の章で紹介する.

ともあれ,そこにたどりつくまで反復処理をすることが待てないというのなら, Enum モジュールや List モジュールでリストのヘルパーを提供していることを知らせておく.

iex> Enum.at [1,2,3], 0
1
iex> List.flatten [1,[2],3]
[1,2,3]

2.2 キーワードリスト

Elixir はキーワードのリストを作るための特別な構文を用意している.

iex> [a: 1, b: 2]
[a: 1, b: 2]

キーワードリストは,2 要素で,最初の要素が atom であるタプル,のリストでしかない.

iex> [head | tail] = [a: 1, b: 2]
[a: 1, b: 2]
iex> head
{ :a, 1 }

Keyword というモジュールは,キーワードリストを操作するための便利な関数をいくつか用意している. そこでは要素の重複を無視して扱ったり,あるいは無視せず扱ったりできる.

iex> keywords = [foo: 1, bar: 2, foo: 3]
iex> Keyword.get keywords, :foo
1
iex> Keyword.get_values keywords, :foo
[1,3]

キーワードリストは関数へ引数として渡すことがとても多いので, 関数へ最後の引数として渡す場合には [] を省略できる. 例えば以下の例は全て正しくて,同じ内容である.

iex> if(2 + 2 == 4, [do: "OK"])
"OK"
iex> if(2 + 2 == 4, do: "OK")
"OK"
iex> if 2 + 2 == 4, do: "OK"
"OK"

2.3 String(バイナリ)とChar の列(リスト)

Elixir において,ダブルクォートされた値とシングルクォートされた値は,異なる.

iex> "hello" == 'hello'
false
iex> is_binary "hello"
true
iex> is_list 'hello'
true

ダブルクォートされた値は string であり,バイナリで表現される. シングルクォートされた値は char list であり,リストで表現される.

事実,ダブルクォートされた値とシングルクォートされた値の両方とも, 単にバイナリとリストの短い表現でしかない.

Elixir では ?aa の ASCII 値を表す.そこで以下のようにも書ける.

iex> is_integer ?a
true
iex> <<?a, ?b, ?c>>
"abc"
iex> [?a, ?b, ?c]
'abc'

このようなケースでは Elixir はバイナリ内の全ての文字が印刷可能なことを調べて,クォートでくくった表現を返す. しかし印刷可能でない文字が含まれていると,違った表し方をされる.

iex> <<?a, ?b, ?c, 1>>
<<97,98,99,1>>

iex> [?a, ?b, ?c, 1]
[97,98,99,1]

Elixir では,明示的に char のリストを反復する場合 ( たまに Elixir から Erlang のコードへのインターフェースで用いる ) 以外ではダブルクォートでくくられた文字列の方が好ましい. リストとしてもバイナリとしてもパターンマッチングはできる.

iex> <<a, b, c>> = "foo"
"foo"
iex> a
102

つまり,バイナリの tail をパターンマッチさせることもできる. はじめの 1 バイトを抽出して,残りのバイナリを取得できる.

iex> <<f :: integer, rest :: binary>> = "foo"
"foo"
iex> f
102
iex> rest
"oo"

上記の例では,バイナリの各セグメントにタグをつけた. 最初のセグメントは整数型 ( デフォルト ) で,文字 “f” の ASCII コードを取得する. 残りのバイト “oo” はバイナリとして rest に割り当てられる.

Elixir でのバイナリ/ビット文字列構文はとてもパワフルで, バイト,ビット,UTF8コードポイントなどでマッチさせることができる. 詳し知りたければ Elixir docs でみることができる.

次は Unicode について進めていこう.

2.4 Unicode のサポート

Elixir の string は,UTF-8 でエンコードされたバイナリになっている. 例えば “é” という文字は 2 バイトの UTF-8 バイナリである.

iex> string = "é"
"é"
iex> size(string)
2

文字列を簡単に操作するために,Elixir は String モジュールを提供している.

# バイト数を返す
iex> size "héllò"
7

# 人間が読んだときの文字数を返す
iex> String.length "héllò"
5

メモ: Elixir では size は事前に値が計算されているものを取得するときに使う. length は値を取得するのに処理が必要なときに使う.

“héllò” の一文字ずつは Unicode codepoint となっている. String.codepoints は String をコードポイント毎の文字に分割する.

iex> String.codepoints "héllò"
["h", "é", "l", "l", "ó"]

Unicode 標準は各文字に対して整数を割り当てる.Elixir はその整数を元に文字を取得したり挿入したりできる.

# コードポイントを求める
iex> ?h
104
iex> ?é
233

# 16進数の値でコードポイントを挿入する
iex> "h\xE9ll\xF2"
"héllò"

UTF-8 はパターンマッチングとうまく動作する. 以下の例では,文字列の最初の UTF-8 コードポイントを抽出して,残りを rest に割り当てる.

iex> << eacute :: utf8, rest :: binary >> = "épa"
"épa"
iex> eacute
233
iex> << eacute :: utf8 >>
"é"
iex> rest
"pa"

一般的に,バイナリと文字列を Elixir で扱うのは簡単だろう. もしもっと生々しくバイナリをいじりたくなった場合は Erlang のバイナリモジュール を用いたり, 文字列として扱いたいなら Elixir の String モジュール を用いることができる.

2.5 ブロック

たいてい,最初に学ぶ制御フローの一つは if 条件だ. Elixir では以下のように書ける.

iex> if true, do: 1 + 2
3

if 式はブロック構文を使っても書ける.

iex> if true do
...>   a = 1 + 2
...>   a + 10
...> end
13

do / end ブロックは,式のまとまりを do: へ渡すための簡単な方法と考えることができる. 以下も全く同じ内容を表す

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

ブロックに else 節を渡すこともできる

if false do
  :this
else
  :that
end

do / end は,常に最も遠い関数呼び出しにバインドされることに気をつけなければいけない. 例えば次の式は

is_number if true do
  1 + 2
end

以下のように解析される

is_number(if true) do
  1 + 2
end

is_number のような,最も遠い関数呼び出しにバインドされることを求めていない場合. 明示的に括弧を追加することでバインドのあいまいさを解消できる.

is_number(if true do
  1 + 2
end)

2.6 フロー制御構造

このセクションでは Elixir の主なフロー制御について詳しくみていく.

2.6.1 パターンマッチング再び

この章の最初のチャプターでいくつかのパターンマッチンングの例をみた.

iex> [h | t] = [1,2,3]
[1, 2, 3]
iex> h
1
iex> t
[2, 3]

Elixir では,Java, Ruby, Python などと異なり = は代入演算子ではない. = は正確に言うと,左辺と右辺がマッチすることをチェックする,マッチ演算子である.

Elixir の多くの制御構造は,異なる節をマッチさせるというパターンマッチングの持っている能力に大きく頼っている. いくつかのケースでは,変数の値にマッチさせたくない場合がある. その場合は ^ 演算子を使って参照専用にもできる.

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

Elixir では,使わない値をマッチさせる場合 _ で表すのが一般的である. 例えば,リストの先頭にだけ興味がある場合,残りの部分にはアンダースコアを割り当てる.

iex> [h | _] = [1,2,3]
[1, 2, 3]
iex> h
1

Elixir では,変数 _ は特別で,どこからも絶対に読まれることがない. 読もうとすると unbound variable error になる.

iex> _
** (ErlangError) erlang error {:unbound_var, :_}

パターンマッチングは強力だが,使用方法には制限がある. 例えばマッチの左側で関数呼び出しを行うことはできない. 以下の例は無効である.

iex> length([1,[2],3]) = 3
** (ErlangError) erlang error :illegal_pattern

2.6.2 ケース

case は,複数のパターンの中からいずれかがマッチするまでの比較に使える.

case { 1, 2, 3 } do
  { 4, 5, 6 } ->
    "This won't match"
  { 1, x, 3 } ->
    "This will match and assign x to 2"
  _ ->
    "This will match any value"
end

= 演算子と同じように,match 節の中で割り当てられた変数は元の値を上書きされる. もし変数に対してパターンマッチしたい場合は ^ 演算子を使う必要がある.

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

それぞれの match 節へ,期待している条件を書くこともできる.(ガード)

case { 1, 2, 3 } do
  { 4, 5, 6 } ->
    "This won't match"
  { 1, x, 3 } when x > 0 ->
    "This will match and assign x"
  _ ->
    "No match"
end

上の例の場合,2 番目の節は x が正の場合にのみマッチする. Erlang VM ではガードに使える式を限定して許可している.

複数の独立したガード節を同時に与えることもできる. 例えば,タプルかリストの最初の要素が 0 であるかどうかチェックする関数について考えてみる. それは以下のように書ける

def first_is_zero?(tuple_or_list) when
  elem(tuple_or_list, 1) == 0 or hd(tuple_or_list) == 0 do
  true
end

しかし,上記の例は常に失敗する. もし引数がリストなら, elem をリストに対して呼び出すとエラーになる. もし要素がタプルなら, hd をタプルに対して呼び出すとエラーになる. これを直すには,2 つの異なった節に書き換える.

def first_is_zero?(tuple_or_list)
    when elem(tuple_or_list, 1) == 0
    when hd(tuple_or_list) == 0 do
  true
end

こうすると,ガードの一つがエラーになっても,次のガードには影響しない.

2.6.3 関数

Elixir では匿名関数は複数の節とガードを受け入れることができる, その形式は先程見た case に良く似ている.

f = fn
  x, y when x > 0 -> x + y
  x, y -> x * y
end

f.(1, 3)  #=> 4
f.(-1, 3) #=> -3

Elixir はイミュータブルな言語であるため,関数のバインディングもイミュータブルである. これは,関数の内側で設定した変数は,関数の外側には影響を及ぼさないことを意味している.

x = 1
(fn -> x = 2 end).()
x #=> 1

2.6.4 受信

次のフロー制御メカニズムは Elixir の actor にとってとても重要なものだ. Elixir ではコードは別々のプロセスで実施され,それぞれにメッセージを交換する. それらのプロセスは OS のプロセスではない ( 比較するととても軽い ) が, プロセス同士が状態を共有しない点が似ているので,そう呼ばれている.

全てのプロセスは,メッセージを交換するために, 受信したメッセージを貯めておけるメールボックスと呼ばれるものを持っている. receive の仕組みによって,与えられたパターンに合致するメッセージがメールボックスの中にあるか探すことができるようになる. 矢印演算子 <- を使ってメッセージをカレントプロセスへ送り,メールボックスからそのメッセージを収集する例を示す.

# 今のプロセス ID を取得する
iex> current_pid = self

# 今のプロセス ID へメッセージを送る,別のプロセスを生成する
iex> spawn fn ->
  current_pid <- { :hello, self }
end
<0.36.0>


# メッセージを収集する
iex> receive do
...>   { :hello, pid } ->
...>     IO.puts "Hello from #{inspect(pid)}"
...> end
Hello from <0.36.0>

あなたの手元では <0.36.0> が戻ってこない場合があるが,似たものになる. もしメールボックスにメッセージがない場合,after 節がなれば,今のプロセスはメッセージが来るまで止まる.

iex> receive do
...>   :waiting ->
...>     IO.puts "This may never come"
...> after
...>   1000 -> # 1 second
...>     IO.puts "Too late"
...> end
Too late

私達が新しいプロセスを生成するのに, spawn 関数へ他の関数を引数として渡して作ったことに注目したい. 後のチャプターで,これらのプロセスについての詳細やメッセージ交換を異なるノード間で行う方法について話す.

2.6.5 Try

try はスローされた値をキャッチするのに使う. 例を見てみよう.

iex> try do
...>   throw 13
...> catch
...>   number -> number
...> end
13

try/catch は, コードが複雑な出口戦略を持っており, スタックから値を戻すのに throw を使った方が簡単である. という,珍しい状況で役に立つ制御メカニズムだ. trycatch の中でガードを使え, after 節はキャッチしたかどうかにかかわらず実行される.

iex> try do
...>   throw 13
...> catch
...>   nan when not is_number(nan) -> nan
...> after
...>   IO.puts "Didn't catch"
...> end
Didn't catch
** throw 13
    erl_eval:expr/3

スローされた値がキャッチされなかった場合,ソフトウェアが停止してしまうことに注意. このような理由から, Elixir はこの節は安全でないと考えており ( 成功するかもしれないし,失敗するかもしれないため ) try/catch/after の中で定義された変数に,外側のスコープからアクセスはできない.

iex> try do
...>   new_var = 1
...> catch
...>   value -> value
...> end
1
iex> new_var
** (UndefinedFunctionError) undefined function: IEx.Helpers.new_var/0

一般的なやり方としては try からの引数を全て明示的に返す

{ x, y } = try do
  x = calculate_some_value()
  y = some_other_value()
  { x, y }
catch
  _ -> { nil, nil }
end

x #=> x が返るか,失敗した場合は nil が返る

2.6.6 If と Unless

上記 4 つの主な制御構造のほかに,日々の仕事の助けとなる制御構造がいくつかある. 例えば ifunless だ.

iex> if true do
iex>   "This works!"
iex> end
"This works!"

iex> unless true do
iex>   "This will never be seen"
iex> end
nil

do/end ブロックは,キーワード表記のショートカットであることを思いだしてもらいたい. だから以下のようにも書ける.

iex> if true, do: "This works!"
"This works!"

あるいはさらに複雑な例を挙げると

# これは以下の例と同じ
if false, do: 1 + 2, else: 10 + 3

# 上と同じ
if false do
  1 + 2
else
  10 + 3
end

エリクサーでは falsenil 以外の全ての値が true となる. つまり if への引数を明示的にブーリアンにしなくてもよい. もし複数の条件のうち,一つが true であることをチェックしたいなら, cond マクロを使うことができる.

2.6.7 Cond

複数の条件を同時にチェックしたいなら, 入れ子になった if 構造のかわりに cond を使うことができる.

cond do
  2 + 2 == 5 ->
    "This will never match"
  2 * 2 == 3 ->
    "Nor this"
  1 + 1 == 2 ->
    "But this will"
end

もしどの条件も true にならなかった場合,エラーが発生する. そのため,普通は常にマッチする true を最後の条件に書く.

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

2.7 組み込み関数

エリクサーはたくさんの組み込み関数を現在のスコープから自動的に使えるようになっている. 上で見たようなフロー制御式に加えて, タプルの中の値を読み書きするのに elemset_elem , バイナリとして指定されたデータ型を表現するのに inspect , など色々ある. これらの関数は KernelKernel.SpecialForms にあり,デフォルトでインポートされている.

これらの関数とフロー制御式は Elixir プログラムを書くために必要なものである. いくつかの場合 Erlang の関数を使う必要があるかもしれない. その方法を見てみよう.

2.8 Erlang の関数を呼びだす

Elixir の資産の一つに Erlang のエコシステムとの統合が簡単なことが挙げられる. Erlang は OTP (Open Telecom Platform) と呼ばれるライブラリ群を出している. 標準ライブラリに加え,OTP は堅牢,分散,フォールトトレラントな OTP アプリケーションをスーパーバイザと共に構築する機能を提供する.

Erlang モジュールはアトム以外の何者でもないので,Elixir からそれらを呼び出すのはとても簡単だ. 例えばモジュール lists の関数 flatten を呼んだり,math モジュールと触れあったりしてみる.

iex> :lists.flatten [1,[2],3]
[1,2,3]
iex> :math.sin :math.pi
1.2246467991473532e-16

Erlang の OTP はとても良く文章化されており, 私達は後程 Mix の章で OTP アプリケーションの作り方について学ぶ.

今はここまで. 次の章では,他のアプリケーションから簡単に再利用できるようにするためのモジュールへのまとめ方を説明する.