Akatsuki Hackers Lab | 株式会社アカツキ(Akatsuki Inc.)

Akatsuki Hackers Labは株式会社アカツキが運営しています。

急いで学ぶElixir#02

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型

truefalseを使用します。

iex> true true iex> true == false false

boolean型であるかどうかをチェックするために、is_boolean/1関数が用意されています。 (Elixirでは、関数を引数の数も含めて指定するときにスラッシュを使用します)

iex> is_boolean(true) true iex> is_boolean(1) false

同様に、is_integer/1is_float/1is_number/1も用意されています。

Atom

Atomはその名前そのものが値となる定数で、他の言語(Ruby等)ではシンボルと呼ばれます。

iex> :hello :hello iex> :hello == :world false

boolean型のtruefalseは実は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Ö"

無名関数

関数はfnendキーワードによって定義されます。

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/1addを渡すと期待通り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, ReferencePIDといったデータ型がありますが、この記事では割愛します。

翻訳元

http://elixir-lang.org/getting-started/basic-types.html