elixirはプログラマの万能薬になるか その2

前回のエントリの続きで、elixirの「ふつうの」言語である側面の説明となる。とはいうものの、関数型言語になじみがない人にとっては新鮮かもしれない。
内容は、Redirecting…とほぼ同じになってしまっている。

データタイプ

基本データタイプ

基本データタイプは以下のとおり。

iex> 1 # integer
iex> 1.0 # float
iex> :atom # atom / symbol 
iex> {1,2,3} # tuple 
iex> [1,2,3] # list
関数

関数オブジェクト(無名関数)もあるが、変数に格納された関数を呼び出すときには、変数と引数をドットで区切る。Erlangではfun() -> body endだが、Elixirではfn() -> body endになる。

iex> x = fn(y,z) -> y + z end 
#Fun<erl_eval.12.111823515>
iex> x(1,2)
** (::UndefinedFunctionError) undefined function: :erl_eval.x/2
    :erl_eval.x/2
    :erl_eval.expr/3
iex> x.(1,2)
3
二種類の文字列

二重引用符はUTF-8の文字列(Erlangのバイナリ)になる。単一引用符の文字列はErlangでいう文字のリストになり、両者は全く違う。

iex> list='abc'
'abc'
iex> bin="abc" 
"abc"
iex> [h|t]=list
'abc'
iex> t         
'bc'
iex> h
97
iex> list==bin      
false

これらのバイナリやリストの文字列を他の文字列に埋め込む事もできる。どちらのタイプかはis_binary/1やis_list/1を使用して調べることができる。

iex> "embedd #{bin}"
"embedd abc"
iex> "embedd #{list}"
"embedd abc"
iex> 'embedd #{list}'
'embedd abc'
iex> 'embedd #{bin}' 
'embedd abc'
iex> is_binary(bin)
true
iex> is_binary(list)
false
iex> is_list(list)  
true
iex> is_list(bin) 
false

既に登場しているが、bool型のtrueとfalseも存在し、is_boolean/1で判定できる。

演算子

数値への演算子

数値に関しては、通常の四則演算子が利用できる。

リスト演算子

リストに対して、++と--が用意されていて、リストの結合と差分をとることができる。この辺はErlangと全く同じ。

iex> [1,2,3] ++ [4,5,6]
[1,2,3,4,5,6]
iex> [1,2,3] -- [2,3]
[1]
iex> [1,2,3] -- [4]  
[1,2,3]
iex> [1,2,3] -- [3,1]
[2]
iex> 
文字列演算子

単一引用符の文字列はリストなので、結合と差分をとることができるが、二重引用符の文字列(バイナリ)ではそんなことは出来まない。そのかわり'<>'という結合演算子が使える。

iex> 'list' -- 'is'  
'lt'
iex> "binary" ++ "bin"     
** (::ArgumentError) argument error
    :erlang."++"("binary", "bin")
    erl_eval.erl:572: :erl_eval.do_apply/6
    src/elixir.erl:70: :elixir.eval_forms/3
    src/elixir.erl:49: :elixir.eval/5
    lib/elixir/iex.ex:21: ::Elixir::IEx.do_loop/2
iex> "binary" <> "bin"
"binarybin"
iex> 
論理演算子

論理演算子の or と and も提供している。当然だが、boolean型についてのみ利用でき、その他の型についての論理演算子の使用はエラーになる。

iex> true or false
true
iex> false and is_binary("abc")
false
iex> 1 and 2
** (::ArgumentError) argument error: 1
    :erl_eval.expr/3
iex> 1 and false
** (::ArgumentError) argument error: 1
    :erl_eval.expr/3
iex> 

or や and はショートカット演算子で、全体を評価しなくても値が決まる場合には、評価されないことがある。この振る舞いは、Erlangのandalsoとorelseにマップされると考えると良い。

iex> true or error("This error will never be raised")
true
iex> false and error("This error will never be raised")
false
iex>
比較演算子

通常の比較演算子==, !=, ===, !===, <=, >=, <, >も用意されている。==と===は整数と浮動小数点数をより厳密に区別することが異なる。

iex> 1.0 == 1
true
iex> 1.0 === 1
false

比較に関しては、異なる型でも順序関係が定義されている。

iex> 1 < :a < fn() -> :a end < {1,2} < 'abc' < "abc"
true

リファレンス(一意のシンボル)やポート(Erlangと外部プログラムのインタフェース)、pid(Erlangプロセスの識別子)を含めてすべての型の順序は以下のとおり。

number < atom < reference < functions < port < pid < tuple < list < bit string

関数呼び出し

関数は引数の数(アリティ)で区別されている。アリティの異なる関数はまったく別の関数として扱われる。関数を呼び出すための括弧は省略できる。

elixirでは入出力や基本的なデータタイプを操作するための小さな標準ライブラリを用意している。これはつまり、複雑なアプリケーションを開発するためにはErlangのライブラリも必要になることを意味している。

ErlangはOTP(Open Telecom Platform)と呼ばれるライブラリ群とともに公開されている。OTPは監視ツリーや分散アプリケーションや耐障害性といった機能を提供している。elixirからErlangの関数を呼び出すのは簡単で、Erlangモジュールにまとめられているので、lists.reverse/1を呼び出すためには以下のようにする。

iex> Erlang.lists.reverse([1,2,3])
[3,2,1]
iex> Erlang.lists.reverse [1,2,3]
[3,2,1]

4.4 パターンマッチ

関数型言語ではおなじみのパターンマッチも使える。

iex> [h | t] = [1,2,3]
[1,2,3]
iex> h
1
iex> t
[2,3]
iex>

elixirでは=は代入ではなく、パターンマッチ演算子になる。パターンに変数がある場合には、できる限りマッチさせるように変数に値が束縛される。どうやっても無理の場合には、エラーになる。

iex> [x,x]=[1,2]
** (::MatchError) no match of right hand side value: [1,2]
    :erl_eval.expr/3

束縛された変数は、再度束縛することができる。ここはErlangと違うところだが、javaRuby使いはほっとするところだろう。

iex> t
[2,3]
iex> [1|t]=[1,2,3,4]
[1,2,3,4]
iex> t
[2,3,4]
iex>

変数の値を固定したままでパターンマッチをしたい場合も多々ある。そういうときには、^演算子を使って変数の束縛を固定する。

iex> t
[2,3,4]
iex> [1|^t|5]=[1,2,3,4,5]
** (::ErlangError) erlang error: :illegal_pattern
    :erl_eval.exprs/2
iex> [1|^t]=[1,2,3,4]
[1,2,3,4]

elixirではアンダースコア変数(_)を代入しても使用しない変数として使う。ここもErlangと同じ。アンダースコア変数は必ずマッチするが、参照することはできない。

iex> [1,_,3|_]=[1,2,3,4]
[1,2,3,4]
iex> _
** (::ErlangError) erlang error: {:unbound_var,:_}
    :erl_eval.exprs/2
iex>

Key-Valueリスト

シンタックスシュガーとして、頻繁に使うアトムをキートしたタプルのリストである[{:key1, value1}, {:key2, value2}]は[key1: value1, key2: value2]と書ける。これらへはOrddictモジュールでアクセスできる。

iex> x = [a: 1, b: 2]
[{:a,1},{:b,2}]
iex> Orddict.get x, :a
1
iex> Orddict.get(x, :a)
1
iex> Orddict.get(x, :c)
nil
iex>

このKey-Valueリストが構文のキーとなる。

4.6 Key-Value引数
例えば、よく知られたif文は以下のように書ける。

iex> if(true,[do: 1+1]) 
2
iex> if(false,[do: 1+1])
nil
iex> 

すなわち、if関数として、do:キーリストを引数とした2変数関数で、第一引数がtrueの場合に、do:キーの値を評価して返すということになる。そして、関数呼び出しは括弧を省略できる。

iex> if true, [do: 1+2]
3
iex>

さらに、最後の引数がKey-Valueリストの場合、カギ括弧も省略できる。

iex> if true, do: 1+2  
3

if関数は第一引数がfalseの場合はelse:キーがあれば、それを評価する。

iex> if( true, [do: 1, else: 2 ] )
1
iex> if( false, [do: 1, else: 2 ] )    
2

do:やelse:の中が複数行とする場合、ブロック記法が使える。

iex> if true do
...> 1
...> elsif: false 
...> 2
...> end
1

実は、ifはelsif:が複数書けたり、else:が書けたり、複雑だったりする。そして、ifはプリミティブではなく、組み込みマクロでelixirで実装されている。

もう一つの論理演算子(||, &&, !)

[sub:論理演算子]で説明した or, and, not は引数がbool型である必要があった。その制限を外して任意の型の引数が使用できる同様の論理演算子 ||, &&, ! を用意している。それらの演算子は falseとnil以外のすべての値をtrueに評価する。

iex> 0 || 1
0
iex> nil || 1
1
iex> false || 1
1
iex> 0 && 1
1
iex> 2 && 1
1
iex> 2 && nil
nil
iex> 2 && false
false
iex> 

case

パターンマッチ演算子=を導入したが、いくつかのパターンにマッチさせたい場合にはcaseを使う。変数がパターン中に現れると、マッチさせるように値を調整可能な場合には、その値に束縛され直す。ここは=と同様だが、Erlangを始めとした関数型言語の多くが単一代入であることと異なっている。

iex> case [1,2,3] do
...> match: [1,2,x] 
...> x
...> match: x
...> x
...> else:
...> 0
...> end
3
iex> case [1,2,3] do
...> match: x
...> x
...> match: [1,x,3]
...> x
...> else:
...> 0
...> end
[1,2,3]

再束縛をしてほしくない場合には、=の時と同様に^演算子を用いる。

iex> x = 4
4
iex> case [1,2,3] do
...> match: [1,x,3] 
...> x
...> match: x
...> x
...> match: _
...> x
...> end
2
iex> x
2
iex> x = 4          
4
iex> case [1,2,3] do
...> match: [1,^x,3]
...> x              
...> match: _       
...> x
...> else:
...> 0
...> end
4
iex> 

Erlangと同様ガード式を使う事もできる。

iex> case [1,2,3] do           
...> match: [1,x,3] when x > 0 
...> x
...> match: x      
...> x
...> end
2

ガード式は、Erlangのガード式と同様に型チェックや論理演算子など、組み込み演算子と関数のみが使える。

制御構造としての関数

関数は第一級のオブジェクトで、[sub:関数]で説明したように、定義はfn()->...endで行い、 =などで変数へ束縛するか、直接.演算子で使用することができる。

iex> (fn(x) -> x*2 end).(3)
6
iex> 

複雑な関数は以下のfn() do...endフォーマットで定義する事もできる。

iex> (fn(x) do
...> x*2      
...> end).(3) 
6
iex> 

関数内で使用される自由変数に対する変更はシャドウされる。

iex> (fn do 
...> x = 2  
...> [x,x=3]
...> end).()
[2,3]
iex> x
1

loop

変数を変更できるプログラム言語では、for文が一般的だが、elixirでは同じことを再帰で行う。Clojureから借りたloop/recurを使って以下のように記述することができる。

iex> loop [1,2,3], 0 do
...> match: [h|t], acc
...> recur(t, h+acc)
...> match: [], acc
...> acc
...> end
6
iex> loop 10, 0 do
...> match: 0, acc
...> acc
...> match: x, acc
...> recur(x-1, acc+x)
...> end
55

細かく言うと、実は無名関数の再帰の問題をまろやかに解決している(同じことをErlangで行おうとすると、ループ用の関数を作成することになる)。

モジュール

elixirで新しいモジュールを作成するためには、”defmodule”マクロに内容を渡すことで行う。

defmodule Math do
 def sum(a,b) do
  a + b
 end
end

iexから対話的に実行するとこうなる。

iex> Hello.world
Hello, world
:ok
iex> defmodule Math do
...> def sum(a,b) do
...>  a + b
...> end
...> end
{:sum, 2}
iex> Math.sum(1,2)
3
iex> 

elixirモジュールの中では以下の定義が可能。

def
公開関数の定義(erlangでいう、export/1宣言も同時にされるイメージ)
defp
プライベート関数の定義
defmacro
マクロの定義
defrecord
レコードの定義
defprotocol
プロトコルの定義
defimpl
プロトコルの実装の定義

これらの定義については別途記述予定。

ネストしたモジュール

elixirでは、他のモジュールの中にネストしたモジュールはその名前に影響を与えない。

defmodule Foo do
 defmodule Bar do
 end
end

上の例は、二つのモジュール”Foo”と”Bar”を定義している。二つ目のモジュールが”Foo::Bar”とよばれていない事に注目。一般にモジュールのネストはelixirでは推奨されていない。実際にやって見ると、

iex> defmodule Math1 do
...>  defmodule Math2 do
...>   def sum(a,b) do   
...>    a+b              
...>   end               
...>  end
...> end
{:sum, 2}
iex> Math1.Math2.sum(1,2)
** error nofile:1: syntax error before: ['Math2']
    src/elixir_errors.erl:18: elixir_errors:syntax_error/4
    src/elixir_translator.erl:6: elixir_translator:parse/3
    src/elixir.erl:76: elixir:eval_/5
    lib/elixir/iex.ex:21: ::Elixir::IEx.do_loop/2
iex> Math1::Math2.sum(1,2)
** error :undef
    ::Math1::Math2.sum(1, 2)
    erl_eval.erl:572: erl_eval:do_apply/6
    src/elixir.erl:80: elixir:eval_/5
    lib/elixir/iex.ex:21: ::Elixir::IEx.do_loop/2
iex> Math2.sum(1,2)      
3
iex> 

このようになる。せっかくネストさせても意味がない。

終わりに

ここまで、elixirの文法のうち、通常の関数プログラミング言語的な概念がruby風味のブロック記法で定義されている事を記述してきた。
次回は、いよいよ、elixir独自の変態的なメタプログラミングを説明する(はず)。