URL
http://elixir-lang.org/getting-started/introduction.html
http://elixir-lang.org/getting-started/basic-types.html
試した環境
Ubuntu Server 14.04 LTS
Introduction
Elixirインストール
Erlang Solutionsのリポジトリを追加してインストールする方法
$ wget https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb && sudo dpkg -i erlang-solutions_1.0_all.deb
$ sudo apt-get update
$ sudo apt-get install elixir
シェルの起動
$ iex
Erlang/OTP 18 [erts-7.0] [source] [64-bit] [async-threads:10] [kernel-poll:false]
Interactive Elixir (1.0.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>
スクリプトの実行
elixirコマンドはelixirスクリプトを実行するコマンド
IO.puts "Hello world
from Elixir"
$ elixir simple.exs
Hello world
from Elixir
elixircはコンパイル用のコマンドで、elixirc hoge.exとするとコンパイルされた.beamファイルが生成される。
.exsはコンパイルしないで実行するスクリプトファイル、.exはコンパイルするファイルに付けるらしい。
Basic types
基本的な型
iex> 1 # integer
iex> 0x1F # integer
iex> 1.0 # float
iex> true # boolean
iex> :atom # atom / symbol
iex> "elixir" # string
iex> [1, 2, 3] # list
iex> {1, 2, 3} # tuple
ちなみに、Erlangのatomの形式は失敗した。
Elixirでは、小文字始まりは変数として扱われるが値が束縛されていないから、関数として処理しようとして、関数も未定義だからundefinedになるみたい。
iex> atom
** (RuntimeError) undefined function: atom/0
基本的な算術
iex> 5 * 5
25
iex> 10 / 2 # Elixirで/演算子はfloatを返す
5.0
iex> div(10,2) # 整数を得たい場合はdivを使う
5
iex> rem 10.3 # 括弧は関数を呼び出すのに必須ではない
1
iex> round 3.58
4
iex> trunc 3.58
3
Booleans
iex> true # trueはこの記述が許されるのか
true
iex> :true # symbolsのtrueもtrue
true
iex> is_boolean(true)
true
iex> is_boolean(1)
false
iex> is_integer(1)
true
iex> is_integer 1.1
false
iex> is_float(1.1)
true
Atoms
atomは名前が値そのものである定数のこと。他の言語ではsymbolsと呼ばれる。
iex> :hello
:hello
iex> :hello == :world
false
iex> true == :true # booleanのtrueはatomだった
true
iex> is_atom(false) # falseはatomなのでtrue
true
iex> is_boolean(:false) # :falseはbooleanなのでtrue
true
Strings
Elixirでの文字列はダブルクオートに囲まれた部分で、UTF-8エンコードされている
iex> "hellö"
"hellö"
iex> "hello #{:world}" # supports string interpolation
iex> "hello
...> world"
"hello\nworld"
IOモジュールのputs/1関数を使うとStringを出力できる
iex> IO.puts "hello\nworld"
hello
world
:ok # IO.putsは:okを結果として返却する
文字列は、内部でバイトのシーケンスのバイナリとして表現される
iex> is_binary("hellö")
true
iex> byte_size "hellö" # öはUTF-8で2バイトなので6になる
6
iex> String.length("hellö") #文字数を調べるとき
5
iex> String.upcase("hellö")
"HELLÖ"
無名関数
関数はfnとendのキーワードによって記述される
iex> add = fn a, b -> a + b end
#Function<12.54118792/2 in :erl_eval.expr/5>
iex> is_function(add)
true
iex> is_function(add,2) #アリティ2の関数であるため、true
true
iex> is_function(add,1)
false
iex> add.(1,2)
3
無名関数を呼び出す時には、変数と関数の間に"."が必要らしい。
無名関数はクロージャなので、関数定義のスコープの中での変数にはアクセスできる。
iex> add_two = fn a -> add.(a,2) end
#Function<6.54118792/1 in :erl_eval.expr/5>
iex> add_two.(2)
4
関数内で割り当てられた変数はそれ以外の変数に影響しない
iex> x = 42
42
iex> (fn -> x = 0 end).()
0
iex> x
42
Lists
リストの記述は角括弧で記述する
リストの値はどのタイプでも指定できる。
iex> [1, 2, true, 3]
[1, 2, true, 3]
iex> length [1, 2, true, 3]
4
リストの連結と削除は++と--が使える
iex> [1,3,4] ++ [5, 6, 7]
[1, 3, 4, 5, 6, 7]
iex> [1, true, 2, false, 3, true] -- [true, false]
[1, 2, 3, true]
リストのheadとtailを抜き出すことはよくある。
headはリストの第一要素で、tailはそれ以外の残りの要素のこと。hd/1とtl/1を使う。
iex> list = [1,2,3]
[1, 2, 3]
iex> hd(list)
1
iex> tl(list)
[2, 3]
iex> hd [] % 空リストは失敗する
** (ArgumentError) argument error
:erlang.hd([])
リストが表示可能なASCII文字コードであれば、文字のリストとして表示する。
ちなみに、Erlangではシングルクオートはatomを表す。
また、Erlangでは文字列は数字のリストとして処理される。
iex> [11, 12, 13]
'\v\f\r'
iex> [104, 101, 108, 108, 111]
'hello'
Elixirでも、シングルクオートとダブルクオートは異なるタイプを表しているが、Elixirではシングルクオートで囲った場合は、文字のリストで、ダブルクオートは文字列である。
iex> 'hello' == "hello"
false
iex> 'hello' == [104, 101, 108, 108, 111]
true
iex> [104, 101, 108, 108, 111] == "hello"
false
タプル
タプルの定義に中括弧を使う。タプルもリストのようにどんな値も持つことができる。
iex> {:ok, "hello"}
{:ok, "hello"}
iex> tuple_size {:ok, "hello"}
2
タプルに格納されている要素は、メモリ内で連続しているで、タプルの要素へのアクセスやタプルのサイズは高速。
iex> tuple = {:ok, "hello"}
{:ok, "hello"}
iex> elem(tuple, 1)
"hello"
iex> elem(tuple, 0)
:ok
iex> elem(tuple, 2)
** (ArgumentError) argument error
:erlang.element(3, {:ok, "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は新しいタプルを返却するが、オリジナルのタプルは変更されない。
これはElixirではデータ型は不変であるため。
不変であることによって、Elixirのコードはどこかの処理でデータ構造が変更されていることを心配する必要がなくなる。
また、不変であることによって、並列コードが競合状態で同時にデータ構造を変更するケースを排除できる。
リストを使うかタプルを使うか
リストはメモリに連結リストとして格納される。
これはリストの各要素は値自身と次の要素へのポインタを保持していることを意味する。
私たちは値とポインターのペアのcons cellを呼び出す。
iex> list = [1|[2|[3|[]]]]
[1, 2, 3]
これはリストの長さへのアクセスは、線形操作である。つまりタプルの大きさを把握するために、リスト全体を横断する必要がある。
要素の先頭に追加する限り、リストの更新は高速である。
iex> [0] ++ list
[0, 1, 2, 3]
iex> list ++ [4]
[1, 2, 3, 4]
上記の例の最初の操作は単純に新しいconsをlistに追加するだけなので高速。2つ目の操作は全体のリストを再構築し新しい要素を最後に追加するため遅い。
一方、タプルはメモリ上に隣接して格納される。これはインデックスによる要素へのアクセスもしくは、タプルサイズの取得が高速であることを意味する。しかし、要素の更新もしくは追加はメモリ上でタプル全体のコピーが必要なため高くつく。
これらのパフォーマンスの特徴はこれらのデータ構造の使い方を決定づける。タプルの一般的な使用ケースは、関数からの追加情報を返却させる場合に使う。例えば File.read/1関数はファイルの内容を読み込み、次のようなタプルを返却する。
iex(2)> File.read("./simple.exs")
{:ok, "IO.puts \"Hello world\nfrom Elixir\"\n"}
iex(3)> File.read("./simple2.exs")
{:error, :enoent}
File.read/1に存在するパスを指定した場合、第1要素にatomの:ok、第2要素にファイルの中身が設定されたタプルを返却する。それ以外の場合は、:errorとエラーメッセージのタプルを返却する。
elem/2関数を使うと、タプルの要素にアクセスできる。しかし、リストには同等のビルトイン関数はない。
iex> tuple = {:ok, "hello"}
{:ok, "hello"}
iex> elem(tuple, 1)
"hello"
データ構造内の要素の数を数えるとき、次のような簡単なルールがある。
一定時間の操作であれば、その関数はsizeと名付けられ、数を数える必要がある操作にはlengthと付けられる。
例えば、byte_size/1(文字列のバイト数)、tuple_size/1(タプルのサイズ)、length/1(リストの長さ)、String.length/1(文字列の文字の数)の数を数える関数が4つある。文字列のバイト数を取得するためにbyte_size/1を使うのは安いが、unicode文字の数を数える場合は文字列全体の反復が必要なので、String.lengthを使う。
データタイプとして、Port、Reference、PIDがある(プロセス間のコミニュケーションに使われる)。これについてはプロセスのところで。
感想(ErlangとElixirの違い)
Erlangの場合atomが小文字始まりで、変数名が大文字始まりだけど、Elixirの場合はatomに":"がつき、変数名は小文字始まり、これはそこまで違和感ない。
Erlangでは'something'はatomだけど、Elixirでは文字のリストで、これはErlangの文字列(二重引用符表記)と同じだが、
Elixirで"somthing"と書くと、Erlangの文字列とは違いutf8のバイナリ表現ということで、少し戸惑いそう。
Elixirの場合
iex> is_list("hello") % リストではない
false
iex> is_binary("hello") % バイナリである
true
iex> is_atom("hello") % もちろんatomでない
false
iex> is_list('hello') % 内部的にリストである
true
iex> is_binary('hello') % バイナリではない
false
iex> is_atom('hello') % もちろんatomでない
false
Erlangの場合
1> is_list("hello"). % 内部表現はリストである
true
2> is_binary("hello"). % バイナリではない
false
3> is_atom("hello"). % もちろんatomでない
false
4> is_list('hello'). % リストではない
false
5> is_binary('hello'). % バイナリではない
false
6> is_atom('hello'). % Erlangではこれはatomである
true