Elixirの型についてちょっと勉強

Tsukasa OISHI

前回までのPhoenixの勉強 (前回の記事 PhoenixとElixirでブログ記事を表示する。) からいったん離れて、Elixir自体の勉強をすすめていきます。何をやっているのか、わからないのは困りますものね。

Elixir本家サイト を参考にしましょう。今日は、Getting Started の第2章、Basic types を勉強しました。

Elixirの型いろいろ

Elixirの基本的な型をさらっと勉強しました。

数値

Elixirの数値には、IntegerとFloatがあります。普通に四則演算子が使えます。

iex(1)> 1 + 2
3
iex(2)> 3 - 1
2
iex(3)> 2 * 3
6
iex(4)> 5 / 2
2.5

iex というのは Rubyでいうところの irb です。
Rubyistは注意が必要ですが、Integerの割り算の結果がFloatになっています。Rubyだと商のIntegerになりますよね。
Elixirで商を得るにはdiv関数を、余りを得るにはrem関数を使います。

iex(5)> div(5, 2)
2
iex(6)> rem(5, 2)
1

ちなみに、Rubyと同様、関数呼び出し時の引数を囲むカッコは省略できるようです。

iex(7)> div 5, 2
2

四則演算のどちらか一方がFloatなら、結果もFloatが返ります。

iex(9)> 1 + 2.0
3.0
iex(10)> 3 - 1.0
2.0
iex(11)> 2 * 3.0
6.0

ブーリアン

真偽値を表す型で、 truefalse があります。

iex(13)> true
true
iex(14)> false == true
false
iex(15)> 1 == 1
true

特に説明は不要ですね。

アトム

RubyでいうところのSymbolに近いです。表記もRubyと同じです。

iex(17)> :kaeru
:kaeru

ブール値である true, false は実はアトムだったりします。

iex(18)> :true == true
true

文字列

ダブルクォーテーションでくくって表します。UTF-8でエンコードされています。文字列は内部的にはバイナリです。

iex(19)> "abcおおいし"
"abcおおいし"

Rubyと同様にコード展開ができます。

iex(20)> val = "うまい"
"うまい"
iex(21)> "くにもとは#{val}"
"くにもとはうまい"

バイト数や文字数を得るには以下のようにします。

iex(22)> byte_size("おおいし")
12
iex(23)> String.length("おおいし")
4

文字数を得るのに、 Stringモジュールのlength関数を使っています。

リスト

リストはいろいろな値を並べたもので、どんな型でも含めることができます。カギ括弧で表します。

iex(33)> [1, 2.5, true, :ok]
[1, 2.5, true, :ok]

lengthで要素数を得ることができます。

iex(34)> length [1, 2, 3]
3

++-- で連結したり引いたりすることができます。

iex(35)> [1, 2, 3, 3] ++ [3, 4, 5]
[1, 2, 3, 3, 3, 4, 5]
iex(36)> [1, 2, 1, 1, 2, 3] -- [1, 1, 2]
[1, 2, 3]

-- のほうはちょっとおやっと思いますね。

リストを扱うときは headtail のお話がよく出てきます。

head はリストの最初の要素を表していて、 tail は2番目以降の残りの要素を表します。
それぞれ、hd関数、tl関数で取得できます。

iex(38)> list = [:ok, 200, "Welcome"]
[:ok, 200, "Welcome"]
iex(39)> hd(list)
:ok
iex(40)> tl(list)
[200, "Welcome"]

タプル

リストと同様、いろいろな型をならべて扱うことができます。中括弧で表します。

iex(42)> {1, 2.5, true, :ok}
{1, 2.5, true, :ok}

リストとタプルの違い

リストとタプルは、まず内部のデータ構造が異なります。

タプルはメモリ上に順番に並んで存在しています。C言語の配列と同じようなものです。配列と同様にインデックスを指定した要素へのアクセスができる(もちろん速い)し、サイズの取得もコストはかかりません。インデックスは 0 から始まります。

iex(43)> tuple = {1, 2.5, true, :ok}
{1, 2.5, true, :ok}
iex(44)> elem(tuple, 1)
2.5
iex(45)> tuple_size(tuple)
4

その代わり、要素の追加や変更はできません。その場合は新しいタプルが作成されることになります。

iex(46)> tuple2 = put_elem(tuple, 2, false)
{1, 2.5, false, :ok}
iex(47)> tuple
{1, 2.5, true, :ok} # 元のタプルは変更されない

put_elem は指定したインデックスの位置の要素を置き換えます。

いっぽう、リストはC言語でいうと構造体とポインタの連結で表す線形リストのようなものです。線形リストだとひとつひとつの要素をノードといったりしますが、Elixirでは cons cell (コンスセル?) と呼ぶようです。
最後の要素にたどりつくためには、先頭の要素から次の要素へと順番にたどっていかなくてはいけません。個別の要素へのアクセスにはコストがかかります(先頭の要素へのアクセスだけは速い)。そのため、リストの先頭に要素を追加するのは速いですが、最後に追加するのは大変です

iex(50)> [0] ++ list
[0, 1, 2, 3] # 速い
iex(51)> list ++ [4]
[1, 2, 3, 4] # 遅い

文字リスト

Elixirには文字リストというものもあります。こちらはシングルクォーテーションでくくります。文字リストと文字列は別モノです。文字列はバイナリですが、文字リストは実際にはただの数値のリストです。

iex(24)> [65, 66, 67]
'ABC'
iex(25)> 'おおいし'
[12362, 12362, 12356, 12375]
iex(26)> 'おおいし' == "おおいし"
false
iex(27)> String.length('おおいし')
** (FunctionClauseError) no function clause matching in String.Graphemes.next_grapheme/1
    (elixir) unicode/unicode.ex:228: String.Graphemes.next_grapheme([12362, 12362, 12356, 12375])
    (elixir) lib/string.ex:932: String.length/1

文字リストはErlangの文字列そのままなので、Erlangの関数へそのまま渡したりできるようです。