君もelixirを始めてみないか
elixirって何?
elixir (エリクサー)はErlangで作られた言語だ。
最初はCoffeeScriptとJavaScriptみたいな関係なのかと思ってくれれば良い。
Erlangの上に作られているから、elixirの特徴を語るにはまずErlangについて述べる必要がある。
Erlangは1986年にJoe Armstrong, Robert Virding, Mike Williams の3人によって最初のバージョンが作られた。
元々は通信機器メーカーのEricsson内部の独自言語だったけれど、1998年にオープンソースとしてリリースされた。
Erlangの特徴をぎゅっとまとめると、並行性と信頼性とホットスワップだ。
Erlangの開発は Joe Armstrong 博士が電話交換制御用ソフトウェアを構築するために、Prologをベースに並行プロセスやエラー処理の仕組みを追加したことに端を発している。
並行性と信頼性、ホットスワップの特徴は通信事業者向けの分散アプリケーションの開発に必要だったんだ。
elixirはBEAMと呼ばれるErlangの仮想環境で動作するから、Erlangの並行性と信頼性の特徴を受け継いでいる。
そして拡張機能としてマクロを使ったメタプログラミング等も扱うことが出来る。
elixirよりまず、Erlangの知名度も高くは無いよね。
でも案外身近なところで使われてたりするんだ。
TwitterではejabberdというJabber/XMPP実装が使われている(今現在もそうかは分からないけど)ようだし、WhatsAppもErlangを使用しているみたいだ。
Erlangは何億人とユーザーを抱えるような大規模なシステムでかなりの実績を積んでいる。
プロセッサのマルチコア化が進む現在、並行性と信頼性を備えるErlangが活躍できる場面が多くなっていくんじゃないかなって思わないかい?
Matzも注目する言語なんて記事もあるよ。
- Rubyist Magazine - Rubyist のための他言語探訪 【第 10 回】 Erlang
- twitterブームの陰で注目を集める“Erlang” - @IT
- 組み込みから生まれた言語Erlangの時代が来る - 日経エレクトロニクス - 日経テクノロジーオンライン
そんなに言うならelixirじゃなくて素のErlangで良いじゃんって思うかもしれない。
答える代わりに質問だ、上の記事は2007の物なんだけど、Erlangって今流行ってるだろうか?
ちょっとこの記事を見て欲しい"Damien Katz氏がApache CouchDBから離反し、Couchbase Server開発を継続“CouchDBがコードの大半をErlangからC/C++するって内容だ。
Damien Katz氏はインタビューの中で流行ってない原因を述べている。
Erlangは素晴らしい言語です。信頼性が高く、信頼できるしっかりとしたシステムを簡単に構築できます。しかし、エコシステムがとても小さいです。なので、ツールや性能に対する投資は他の一般的な言語とは比べようもないくらい少ないです。私はErlangにもっと人気の言語になって欲しいと思います。ErlangがJavaよりも速くならない理由はどこにもありません。しかし、その奇妙な構文が人々を遠ざけ、普及と商用投資を妨げています。でも私はErlangが好きです。性能が重要なコンポーネントでの利用は少なくしますが、今後も致命的に重要なコンポーネントに使うつもりです。
そう、流行らないのは取っつきにくいからだ。
であれば、取っつきやすければ Erlang のパワーを享受できるようになる。
そういう考えで始まったプロジェクトにはReia Programming Languageという物もある、しかし残念ながらReiaの開発は停止してしまった。
Reiaの後に登場し、後継のプロジェクトに指名され、2014年9月18日にv1.0.0がリリースされたのがelixirなんだ。
開発環境は整ってるの?
何が取っつきやすくなったかって、それはもちろん文法なんだけど、今や言語そのものが良くたって開発環境が良くないと流行らないよね。
だから文法の前に開発の環境について紹介しておくね。
僕は普段はrubyを使っているから、rubyの環境と対比させて紹介するね。
まずは Mix
だ。
Mix
は ruby だと rake
と bundler
を合わせたようなものだ。
rubyのプロジェクト開始は bundle init
して、Gemfile
を編集して、bundle install
するよね。
$ mkdir ruby_app
$ cd ruby_app
$ bundle init
$ vim Gemfile
source "https://rubygems.org"
gem "rails"
$ bundle install
elixir
の mix
の場合は mix new elixir_app
して mix.exs
の deps
を編集して、mix deps.get
だ。
ここでは deps
に elixir-lang/ecto というライブラリを追加している。
$ mix new elixir_app
$ cd elixir_app
$ vim mix.exs
defmodule ElixirApp.Mixfile do
use Mix.Project
def project do
[app: :elixir_app,
version: "0.0.1",
elixir: "~> 1.0",
deps: deps]
end
# Configuration for the OTP application
#
# Type `mix help compile.app` for more information
def application do
[applications: [:logger]]
end
# Dependencies can be Hex packages:
#
# {:mydep, "~> 0.3.0"}
#
# Or git/path repositories:
#
# {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"}
#
# Type `mix help deps` for more examples and options
defp deps do
[
{:ecto, "~> 0.4"}
]
end
end
$ mix deps.get
rubyでのRubyGems.orgに対応するライブラリのホスティングはHexという物があるし、
依存関係には GitHub のリポジトリを指定することも出来る。
インタラクティブシェルは irb
に相当する iex
が用意されているし、テストも標準で ExUnit
という物がある。
さらに、是非紹介しておきたいのが Elixir Release Manager bitwalker/exrmだ。
詳しい使い方は後に回すけれど、exrm はリリース用のビルドを行うことが出来るし、さらにダウンタイムなしのアップグレードやダウングレードを簡単にしてくれる。
機能 | ruby | elixir |
---|---|---|
ビルドツール | rake | mix |
ライブラリ管理 | bundle | mix |
標準リポジトリ | RubyGems.org | Hex |
インタラクティブシェル | irb | iex |
テスト | minitest | ExUnit |
エディターのサポートは、vim(vim elixir-lang/vim-elixir)と emacs(elixir-lang/emacs-elixir)とAtom(lucasmazza/language-elixir)があるから大丈夫かな?(他にもgeditとTextmateも用意されているよ)
はじめてみよう
始めて見るにはまずelixirをインストールしないとね。
もしMacを使っていて、すでにbrew
を使っているのであればとても簡単
$ brew update
$ brew install elixir
Ubuntu を使っているなら、リポジトリを追加してapg-get
で入る
$ wget http://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
Windowsの場合はインストーラーが用意されているよ。
これで基本的にはErlangも一緒に入っている。
早速使い始めてみよう。iex
コマンドでインタラクティブシェルが立ち上がる
Erlang/OTP 17 [erts-6.3] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]
Interactive Elixir (1.0.2) - press Ctrl+C to exit (type h() ENTER for help)
iex>
終了したいときはCtrl+C
して(a)bort
のa
をタイプしてエンターだ。
実はErlang(erl
で起動するよ)だと式の最後に.
ピリオドを付けなきゃならない
Erlang/OTP 17 [erts-6.3] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]
Eshell V6.3 (abort with ^G)
1> 1+2.
3
でもelixirならピリオドはいらない、そのまま1+2
とタイプしてエンターで計算できる。
iex> 1+2
3
このまま進む前に、elixirのインストールによって入った他のコマンドも紹介しておこう。
elixir
とelixirc
だ。
elixir
コマンドはelixirスクリプトを実行する物だ。
ruby hoge.rb
みたいな感じで、elixir hoge.exs
と使用する。
elixirc
はコンパイル用のコマンドで、elixirc hoge.ex
とするとコンパイルされた.beam
ファイルが生成される。
ファイルの拡張子を.exs
と.ex
を登場させたけれど、
.exs
はコンパイルしないで実行するスクリプトファイル、
.ex
はコンパイルするファイルに付けるのが習慣なんだ。
他には今後よく使う表記について説明しておこう。
elixirでは関数は"モジュール名”, “関数名”, “アリティ"(つまり引数の個数)によって一意に定まる。
だから関数を表現するときに モジュール.関数/アリティ
といった具合に表記する。
例えばIO.puts/1
とかね。
これはiexからドキュメントを見るときにも使うよ。
iex> h(IO.puts/1)
def puts(device \\ :erlang.group_leader(), item)
Writes the argument to the device, similar to write/2, but adds a newline at
the end. The argument is expected to be a chardata.
まずは基本的な型だ
整数、浮動小数、文字列
基本的な型を確認してみよう
iex> 1+2
3
iex> 1+2.0
3.0
iex> "string"
"string"
iex> 1 + "string"
** (ArithmeticError) bad argument in arithmetic expression
:erlang.+(1, "string")
整数、浮動小数点、文字列が使えることが分かった。
整数と浮動小数点の計算ではエラーにならずに自動的に型の変換もしてくれる。
暗黙に変換できな場合はもちろんエラーになる。
ちなみにelixirの浮動小数点は64ビットの倍精度だ。
四則演算は当然出来るんだけど、ちょっと注意なのが/
による除算だ。
irbで10 / 3
とやれば3
が出力されるけど、iexなら浮動小数点で返る。
整数の商が欲しければdiv
関数が用意されている。
rubyと同じで自明なカッコは省略も出来る。
iex> 10 / 3
3.3333333333333335
iex> div(10, 3)
3
iex> div 10, 3
3
二進数、八進数、十六進数、そして指数表記もサポートしている。
iex> 0b11111011111
2015
iex> 0o77
63
iex> 0xff
255
iex> 1.0e-10
1.0e-10
真偽値
真偽値はtrue
,false
だ。
iex> true
true
iex> false
false
真偽値では無いけれどnil
も用意されている
iex> nil
nil
論理演算はand
,or
,not
だ。
iex> true and false
false
iex> true or false
true
iex> not false
true
論理演算には&&
,||
,!
も用意されている。
違いは真偽値だけ受け付けるか、任意の型を受け付けるかだ。
iex> 1 and true
** (ArgumentError) argument error: 1
iex> 1 && true
true
&&
,||
,!
の場合、false
とnil
以外を真として評価する。
rubyと一緒だね。
iex> not 1
** (ArgumentError) argument error
:erlang.not(1)
iex> ! 1
false
or
, and
, ||
, &&
は短絡演算子、つまり左辺が条件を満たさない場合は右辺は評価されない。
iex> x
** (RuntimeError) undefined function: x/0
iex> true or x
true
iex> true || x
true
ここまで紹介してきた型はis_integer/1
, is_float/1
, is_number/1
, is_boolean/1
で判定できるよ。
is_number/1
は整数もしくは浮動小数点の場合にtrue
だね。
iex> is_number(1)
true
iex> is_number(1.0)
true
iex> is_number(true)
false
アトム
次はアトムだ。アトムは:
から始まるもので、rubyだとシンボルって呼ばれているね。
真偽値のtrue
, false
は実はアトムなんだ。
iex> true == :true
true
iex> is_atom(false)
true
iex> is_boolean(:false)
true
iex> is_atom(:hoge)
true
iex> is_boolean(:hoge)
false
文字列の式展開 / 引用符による違い
文字列は二重引用符で括って使う。
文字列の結合は+
ではなく<>
だ。
rubyと同様に#{}
によって式を展開することも出来る。
単一引用符を使おうとすると、少し驚く結果となるだろう。
iex> "foo" + "bar"
** (ArithmeticError) bad argument in arithmetic expression
:erlang.+("foo", "bar")
iex> "foo" <> "bar"
"foobar"
iex> "1 + 2 = #{1+2}"
"1 + 2 = 3"
iex '日本語'
[26085, 26412, 35486]
iex> "日本語"
"日本語"
これは文字列と文字のリストの違いが原因なんだ。
リストとタプル
単一引用符を使ったときに現れたものはリストなんだ。
リストは[]
を使って表現する。リストには何でも入れられる
iex> [1, true, :atom, "string", [1,2]]
[1, true, :atom, "string", [1, 2]]
リストと似ているタプルという物もある。
タプルもリストと同じように何でも入れられる
iex> {1, true, :atom, "string", [1,2], {:x, :y}}
{1, true, :atom, "string", [1, 2], {:x, :y}}
似ているけれど、中身は違う。
それはリストは中身が連結リスト(Linked list)であるのに対し、タプルは要素を連続したメモリ上に保存している点だ。
タプルは連続したメモリ上に要素を持っているから、インデックスによる要素の取得や、要素数の取得は高速に出来る。
対してリストは連結リストであるため、要素を順にたどる必要があり、タプルよりは低速になってしまう。
リストの方が使い勝手が良い場合は要素を更新したり追加する時だ。
elixrのデータ型は変更不可能なため、タプルの要素の更新はタプル全体をコピーしてから行う必要がある。
リストの場合は途中の連結を変更するだけなので、タプルより簡単だ。
リストとタプル、似てはいるけれど、適切に使い分けた方が良い。
つまり変更されないような場合はタプル、変更される場合はリストといった具合だ。
リストとタプルについての操作も見てみよう。
リストは++/2
, --/2
で連結や、差し引いたりが出来るし、hd/1
, tl/1
でリストの先頭とそれ以外の取得が出来る(LISPのcar, cdrみたいだね)。
長さの取得はlength/1
を使う
iex> [1,2,3] ++ [3,4,5]
[1, 2, 3, 3, 4, 5]
iex> [1,2,3] -- [3,4,5]
[1, 2]
iex> hd [1,2,3,4,5]
1
iex> tl [1,2,3,4,5]
[2, 3, 4, 5]
length [1,2,3]
3
タプルのインデックスによる要素取得、サイズ取得、要素の変更はそれぞれelem/2
, tuple_size/1
, put_elem/3
を使う。
iex> elem({1,2,3}, 1)
2
iex> tuple_size({1,2,3})
3
iex> put_elem({1,2,3},2,4)
{1, 2, 4}
リストの要素数取得がlength
で、タプルの要素数取得がsize
なのはちゃんと理由があるんだ。
elixirではデータ構造が持っている要素数を数える場合、
値があらかじめ計算されている場合は関数名にsize
を、明示的に計算が必要なものは関数名にlength
が使われている。
自分で関数を作る場合にも気をつけてみよう。
比較演算
比較演算子には==
,!=
,===
,!==
,<=
,>=
,<
, >
が用意されている。
基本的には想像通りの動作をする。
iex> 1 == 1
true
iex> 1 != 1
false
iex> 2 <= 2
true
iex> 2 < 2
false
==
と===
の違いは===
の方が整数と浮動小数点数を厳密に比較する点だ。
iex> 1 == 1.0
true
iex> 1 === 1.0
false
おもしろいのは異なる型の比較が出来る点だ。
iex> 1 < :x
true
これは利便性のためで、以下のような型の比較ルールが用意されている。
number < atom < reference < functions < port < pid < tuple < maps < list < bitstring
パターンマッチング
今までのコードの中で、プログラムではよく使うのに、あえて登場させなかった演算子がある。=
だ。
使うのは簡単
iex> x = 1
1
iex> x
1
結果も至極当然に見える。でも、次の例はどうだろうか
iex> 1 = x
1
もしこれをirb
で動かしていればシンタックスエラーだ。
irb(main)> x = 1
=> 1
irb(main)> 1 = x
SyntaxError: (irb):3: syntax error, unexpected '=', expecting end-of-input
1 = x
^
でもelixirでは1 = x
はエラーにはならなかった。
実はelixirの(もしくはErlangの)=
は代入の演算子ではなく、パターンマッチングを行うマッチ演算子なんだ。
1 = x
はパターンマッチングとして成立しているからエラーにはならない。
2 = x
はマッチしないからエラーとなる。
iex> 2 = x
** (MatchError) no match of right hand side value: 1
マッチ演算子の左辺に変数があり、パターンマッチングが成立すれば変数に割り当てがされる。
iex> x = 2
2
iex> x
2
当然じゃ無いか、これなら代入と同じだと思うかもしれない、でもErlangよりはとっつきやすい。
もしErlangで同じ事を実行したなら
注: Erlangの変数は大文字で始まる(Prologみたいだね)、そして式の最後にはピリオドがいる
Eshell V6.3 (abort with ^G)
> X = 1.
1
> X.
1
> 1 = X.
1
> 2 = X.
** exception error: no match of right hand side value 1
> X = 2.
** exception error: no match of right hand side value 2
> X.
1
そうErlangではX = 2
でもエラーとなる。
全ての変数はイミュータブルであり、代入は1回だけに限られている。
不便に思えるかもしれないけれど、関数型言語の考えを使うなら便利にもなる。
elixirではピン演算子^
によって再束縛をコントロールすることが出来る。
iex> ^x = 3
** (MatchError) no match of right hand side value: 3
iex> x
2
徐々にパターンマッチングが代入と異なる点に踏み込んでみよう。
マッチ演算子はリストやタプルの構造でも使うことが出来る。
iex> {x, y, z} = {1, :hoge, "bar"}
{1, :hoge, "bar"}
iex> x
1
iex> y
:hoge
iex> z
"bar"
これだけだと一気に代入したのと変わらないように見えるけれど、左辺に変数以外があったらどうなるだろう。
iex> {:name, x} = {:name, "taro"}
{:name, "taro"}
iex> x
"taro"
iex> {:name, x} = {:age, 1}
** (MatchError) no match of right hand side value: {:age, 1}
この例だとタプルの先頭要素が:name
の時のみ、変数x
に割り当てが出来ると言うことだ。
左辺に同じ変数を複数回使ったなら、同じものに束縛できなければパターンマッチングは成功しない。
iex> {x, y, x} = {1, 2, 3}
** (MatchError) no match of right hand side value: {1, 2, 3}
iex> {x, y, x} = {1, 2, 1}
{1, 2, 1}
ピン演算子^
は以前に束縛した値とのマッチングを表現できる。
iex> x = 1
1
iex> {x, x} = {2, 1}
** (MatchError) no match of right hand side value: {2, 1}
iex> {x, ^x} = {1, 1}
{1, 1}
iex> {x, ^x} = {2, 2}
** (MatchError) no match of right hand side value: {2, 2}
iex> {x, ^x} = {2, 1}
{2, 1}
iex> x
2
パターンマッチングにおいて特殊な変数に_
がある。
これは簡単に言えばなんでもいいってことだ。
iex> {y, y, x} = {1, 2, 3}
** (MatchError) no match of right hand side value: {1, 2, 3}
iex> {_, _, x} = {1, 2, 3}
{1, 2, 3}
iex> x
3
リストのパターンマッチング
タプルと同じようにリストだってパターンマッチング出来る。
iex> [x, y, z] = [1, :hoge, "bar"]
[1, :hoge, "bar"]
リストの中身は連結リストだという話をしたよね、だから「先頭要素」と「続くリスト」というマッチングも出来る。
iex> [head | tail] = [1, :hoge, "bar"]
[1, :hoge, "bar"]
iex> head
1
iex> tail
[:hoge, "bar"]
[head | tail]
と指定したなら、先頭も続きも無い空のリストとはマッチングしない
iex> [head | tail] = [1]
[1]
iex> tail
[]
iex> [head | tail] = []
** (MatchError) no match of right hand side value: []
[head | tail]
の書き方は実はマッチング以外にもリストそのものの表記としても使うことができる。
簡単な例は先頭への要素の追加だ。
iex> list = [1,2,3]
[1, 2, 3]
iex> [0 | list]
[0, 1, 2, 3]
[1 | [2]]
と[1 | 2]
は意味が違ってしまうから気をつけよう
iex> [1 | [2]]
[1, 2]
iex> [1 | 2]
[1 | 2]
キーワードリストとマップ
リストは登場したけれど、まだ連想配列(RubyだとHash)を紹介していなかったね。
elixirではキーワードリストとマップと呼ばれる2種類の連想データ構造があるんだ。
キーワードリスト
まずはキーワードリストだ。
elixirでは,「最初の要素がアトムとなっているタプル」のリストをキーワードリストと呼ぶ。
iex> k_list = [{:x, 20}, {:y, 30}]
[x: 20, y: 30]
キーワードリストを使うとき、省略した記法もサポートされている。
iex> k_list = [x: 20, y: 30]
[x: 20, y: 30]
要素へのアクセスも出来る。指定のキーが無い場合はnil
になる。
rubyのHashと同じような使い勝手だね。
iex> k_list[:x]
20
iex> k_list[:z]
nil
キーワードリストはあくまで「タプルのリスト」だから、操作もリストと同じだ。
iex> k_list ++ [z: 40]
[x: 20, y: 30, z: 40]
iex> [{:z, 40} | k_list]
[z: 40, x: 20, y: 30]
あくまでリストだから、キーの順番は保たれるし、同じキーが複数回あってもいい。
キーによるアクセス時、同一のキーがキーワードリスト内部にあっても、前方の値が優先される。
iex> new_list = k_list ++ [z: 40, x: 10]
[x: 20, y: 30, z: 40, x: 10]
iex> new_list[:x]
20
マップ
マップは%{}
を使うと定義することができる。
iex> %{:x => 20, :y => 30}
%{x: 20, y: 30}
キーワードリストでは最初の要素がアトムだったがm、マップではキーをどんな値にもする事ができる。
キーワードリストはキーの順序が保たれ、複数の同一キーも許された、
しかしマップではキーの順序は保たれず、もしマップを作るときに同じキーが渡されると最後の一つが優先される。
iex> %{:x => 20, :x => 30}
%{x: 30}
マップのキーが全てアトムの場合はrubyのhash記法のように省略を使うことが出来る。
iex> %{x: 20, y: 30}
%{x: 20, y: 30}
マップは与えられたマップのキーがある部分にだけマッチするので、パターンマッチングに使いやすい。
iex> %{name: n, age: x} = %{name: 'taro', age: 30}
%{age: 30, name: 'taro'}
iex> n
'taro'
iex> x
30
iex> %{name: n} = %{name: 'taro', age: 30}
%{age: 30, name: 'taro'}
iex> n
'taro'
iex> %{name: n} = %{age: 30}
** (MatchError) no match of right hand side value: %{age: 30}
マップにはキーにアクセスするための構文が提供されている。
# 要素へのアクセス
iex> map = %{name: 'taro', age: 30}
%{age: 30, name: 'taro'}
iex> map.name
'taro'
iex> map.age
30
iex> map.x
** (KeyError) key :x not found in: %{age: 30, name: 'taro'}
# 要素の更新
iex> %{map | name: 'ziro'}
%{age: 30, name: 'ziro'}
iex> map
%{age: 30, name: 'taro'}
iex> %{map | x: 'xxx'}
** (ArgumentError) argument error
(stdlib) :maps.update(:x, 'xxx', %{age: 30, name: 'taro'})
(stdlib) erl_eval.erl:255: anonymous fn/2 in :erl_eval.expr/5
(stdlib) lists.erl:1261: :lists.foldl/3