Elixirの基本型
以下の基本型があります。
- integer : 1
, 0x1F
- float : 1.0
- boolean : true, false
- atom (symbol) : :atom
- string : "Elixir"
- list : [1, 2, 3]
- tuple : {1, 2, 3}
演算
iex> 1 + 2
3
iex> 5 * 5
25
iex> 10 / 2
5.0
Elixirでは/
は必ずfloatを返します。
整数の商や余剰を得るには、div
またはrem
を使用します。
iex> div(10, 2)
5
iex> div 10, 2
5
iex> rem 10, 3
1
このように、Elixirでは関数を呼び出すのに括弧を省略できます。 2進数や8進数、16進数は以下のように使用でいます。
iex> 0b1010
10
iex> 0o777
511
iex> 0x1F
31
小数点を使用するとfloat型になり、指数形式を使用することもできます。
iex> 1.0
1.0
iex> 1.0e-10
1.0e-10
float型は64ビット精度です。
四捨五入にはround
、切り捨てにはtrunc
を使用します。
iex> round 3.58
4
iex> trunc 3.58
3
Boolean型
true
とfalse
を使用します。
iex> true
true
iex> true == false
false
boolean型であるかどうかをチェックするために、is_boolean/1
関数が用意されています。
(Elixirでは、関数を引数の数も含めて指定するときにスラッシュを使用します)
iex> is_boolean(true)
true
iex> is_boolean(1)
false
同様に、is_integer/1
、is_float/1
、is_number/1
も用意されています。
Atom型
Atomはその名前そのものが値となる定数で、他の言語(Ruby等)ではシンボルと呼ばれます。
iex> :hello
:hello
iex> :hello == :world
false
boolean型のtrue
とfalse
は実はAtomです。
iex> true == :true
true
iex> is_atom(false)
true
iex> is_boolean(:false)
true
String型
Elixirではダブルクオーテーションを用いて文字列を表現します。 UTF-8でエンコードされます。
iex> "エリクサー症候群"
"エリクサー症候群"
iex > a = 3
3
iex > "エリクサー症候群#{a}"
"エリクサー症候群3"
文字列中に改行を入れたり、エスケープシーケンスを使用することができます。
iex> "hello
...> world"
"hello\nworld"
iex> "hello\nworld"
"hello\nworld"
IO.puts/1
を使用して文字列を出力することができます。
iex> IO.puts "hello\nworld"
hello
world
:ok
Elixirの文字列は内部ではバイト列のバイナリとして扱われます。
iex> is_binary("hellö")
true
文字列の長さはString.length/1
で得ることができます。
iex > String.length("エリクサー")
5
その他、UTF-8に対応したString.upcase/1
が用意されてます。
iex> String.upcase("hellö")
"HELLÖ"
無名関数
関数はfn
とend
キーワードによって定義されます。
iex> add = fn a, b -> a + b end
Function<12.71889879/2 in :erl_eval.expr/5>
iex> is_function(add) true iex> is_function(add, 2) true iex> is_function(add, 1) false iex> add.(1, 2) 3
Elixirでは関数は"first class citizens"に分類されるので、integer型やstring型と同様に引数として関数に渡すことができます。
したがってis_function/1
にadd
を渡すと期待通りtrue
を返します。
is_function/2
の第二引数によってadd
に対して定義されている引数の数を確認することができます。
無名関数を呼び出すためには、.
(ドット)を使用する必要があります。
無名関数はクロージャーなので、 定義された時点でスコープに含まれる変数にアクセスすることができます。
iex> add_two = fn a -> add.(a, 2) end
Function<6.71889879/1 in :erl_eval.expr/5>
iex> add_two.(2) 4
関数内での変数への代入は、その外側の環境内の変数には影響しません。
iex> x = 42
42
iex> (fn -> x = 0 end).()
0
iex> x
42
リスト型
[]
を使用してリストを定義します。リストの値はどんな型でも使用できます。
iex> [1, 2, true, 3]
[1, 2, true, 3]
iex> length [1, 2, 3]
3
++/2
と--/2
演算子を使用することで、二つのリストを結合することができます。
iex> [1, 2, 3] ++ [4, 5, 6]
[1, 2, 3, 4, 5, 6]
iex> [1, true, 2, false, 3, true] -- [true, false]
[1, 2, 3, true]
hd/1
及びtl/1
(head, tail)を使用し、リストの先頭及び先頭以外の要素を取り出すことができます。
iex> list = [1,2,3]
iex> hd(list)
1
iex> tl(list)
[2, 3]
以下のようにリストを戻り値がシングルクォートで囲われた値になる場合があります。
iex> [11, 12, 13]
'\v\f\r'
iex> [104, 101, 108, 108, 111]
'hello'
ElixirはASCII数字のリストを認識したとき、それをcharのリストとして出力します。 シングルクォートとダブルクォートの文字列は別々の扱いとなります。
iex> 'hello' == "hello"
false
Tuple型
Tupleは{}
を使用して定義します。
リストと同様に、Tupleの値の型は問いません。
iex> {:ok, "hello"}
{:ok, "hello"}
iex> tuple_size {:ok, "hello"}
2
Tupleはメモリ上の隣接した位置に要素を格納します。 したがって、インデックスによる要素の取得及び要素数の取得を高速に実行することができます。
iex> tuple = {:ok, "hello"}
{:ok, "hello"}
iex> elem(tuple, 1)
"hello"
iex> tuple_size(tuple)
2
put_elem/3
により特定の位置に要素を格納することも可能です。
iex> tuple = {:ok, "hello"}
{:ok, "hello"}
iex> put_elem(tuple, 1, "world")
{:ok, "world"}
iex> tuple
{:ok, "hello"}
put_elem/3
は新しいTupleを返しましたね。
tuple
変数に格納した値は変更されませんでした。
これは、Elixirではデータ型はimmutableだからです。
この性質があるので、Elixirのコードは変数の値が勝手に変わってしまわないかという心配をする必要がありません。
また、immutableの性質により、並列処理において複数のエンティティが同じデータ構造を変えてしまう心配もしなくてよくなります。
ListかTupleか
ListとTupleの違いはなんでしょうか。
Listはメモリ上でリスト構造としてデータを格納します。 すなわち、各要素はその値と次の要素へのポインタを保持します。 要素と次の要素へのポインタのペアを"cons cell"と呼びます。
iex> list = [1|[2|[3|[]]]]
[1, 2, 3]
したがって、リストの長さを計算するのは線形計算となります。 データを先頭に追加する分には、高速な処理となります。
iex> [0 | list]
[0, 1, 2, 3]
一方でTupleは、メモリ上に並んで格納されます。 したがってTupleのサイズを取得したり、インデックスによって要素を取得するのは高速な演算となります。 ところが、要素を更新したり追加したりする処理は、Tupleの全データをメモリ上にコピーする必要があるために、 計算コストが高くなります。
このようなパフォーマンスの違いを踏まえ、それぞれのデータ構造を使い分けます。 Tupleがよく使われるケースとして、関数の戻り値が挙げられます。
iex> File.read("path/to/existing/file")
{:ok, "... contents ..."}
iex> File.read("path/to/unknown/file")
{:error, :enoent}
ほとんどの場合、Elixirが用意した関数を使うことで適切なデータ構造を選ぶことになるでしょう。
例えばelem/2
はTupleに対して用意されてますが、Listに対しては用意されてません。
iex> tuple = {:ok, "hello"}
{:ok, "hello"}
iex> elem(tuple, 1)
"hello"
要素数を数える場合、Elixirでは定数時間の演算に対してはsize
、線形時間の場合はlength
という命名をします。
最後に、ElixirではPort
, Reference
、PID
といったデータ型がありますが、この記事では割愛します。