Elixir(関数型プログラミング言語):Dev Basics/Keyword
Elixirは、動的型付けを持つ関数型言語だ。スケーラビリティ、耐障害性、並行プログラミングなどの特徴を持つ。
Elixirとは
Elixirは、動的型付けを持つ関数型言語だ。スケーラビリティ、耐障害性、並行プログラミングなどの特徴を持つ。Erlangで実装され、Erlang VM上で動作する。
Elixirの特徴
Elixirの特徴を以下に挙げる。
- 関数型言語
- パターンマッチ
- ガード節
- パイプ
- Erlangとの互換性
- 軽量なプロセス
ElixirはErlangを用いて実装されていて、Erlang VM上で動作する。このVMが提供するプロセスは非常に軽量で、数万〜数十万のプロセスを1台のコンピュータ上で同時に実行させることも可能だ。そして、あるプロセスは他のプロセスからは完全に分離されている。つまり、あるプロセスが特定のメモリ領域を別のプロセスと共有することはない(あるプロセスから別のプロセスのデータを使いたい場合には、メッセージを使用)。このことから、Elixirではマルチプロセス/マルチスレッドプログラミングにありがちなデッドロックなどの問題を克服できるとともに、スケーラブルなアプリを容易に実装できる。
また、Elixirは関数型言語であり、そのデータ型は不変性を持つ。ある変数の値は(その変数に新たな値を「束縛」するまでは)不変であり、その変数を何らかの関数に渡したときの結果は常に同じになる。C#などの言語に見られる「このメソッドはスレッドセーフ、このメソッドはスレッドセーフではない」といったことを気にする必要はない。プロセスが他のプロセスから独立した存在であることに加えて、この不変性がElixirの並行プログラミングのしやすさを支えているといえる。
以下ではElixirの特徴の幾つかを実際のコードで見ていくことにしよう。ここではElixirの対話環境である「iex」を主に使用していく。また、Elixirのインストールなどについての説明は割愛する。
代入ではなくパターンマッチ
Elixirには「代入」という概念はない。変数はある値を「束縛」する。そして、束縛に使われるのが「パターンマッチ」だ。以下に簡単な例を示す。
iex(1)> a = 100
100
iex(2)> a
100
変数aに新しい値を束縛する。
この例は変数aに値100を束縛している。見た目は代入と変わらないが、実際にはこれは=演算子の左辺と右辺をマッチさせ、左辺と右辺が等しくなるように変数aの値を100に束縛している。このことからElixirでは=演算子を「マッチ演算子」と呼ぶ。これは代入ではないので、以下のような記述も可能だ。
iex(3)> 100 = a
100
既に変数aの値は100であるため、このパターンマッチは成功する。ただし、変数の値が束縛されるのは変数が左辺値であるとき(この場合はマッチ演算子の左辺にあるとき)だけである。そのため、未定義の変数を右辺に記述はできない。
iex(4)> 100 = b
** (CompileError) iex:4: undefined function b/0
マッチ演算子の右辺には未定義の変数は書けない。
一度、値を束縛した変数に、新たな値を束縛することも可能だ。パターンマッチによる束縛を防ぐにはピン演算子(^演算子)を使用する。
iex(4)> a = "string"
"string"
iex(5)> a
"string"
iex(6)> ^a = 100
** (MatchError) no match of right hand side value: 100
最後の例では、ピン演算子を使い、既に定義されている変数aの値を用いてパターンマッチを行っているのでエラーが発生している(これに対して、「iex(4)」では変数aに新たな値を束縛している)。
無名関数とガード
Elixirの関数には関数(名前付き関数)と無名関数がある。名前付き関数はモジュール内にdefキーワードを使用して定義する(プログラムを構造化する際に使用する。なお、名前付き関数は本稿では取り上げない)。一方、無名関数はfnキーワードを使用して定義する。以下に例を示す。
iex(6)> hello = fn
...(6)> -> IO.puts "Hello"
...(6)> end
#Function<20.52032458/0 in :erl_eval.expr/5>
iex(7)> hello.()
Hello
:ok
この例では、変数helloにこれはコンソールに「Hello」を出力する無名関数を束縛している。そのボディーである「-> IO.puts "Hello"」がこの無名関数の内容である。この無名関数はパラメーターを取らないので「->」の左側には何もないがパラメーターを持つ場合には左側にパラメーターリストを記述する(かっこは省略可能。後述)。また、上を見ると分かるように、無名関数の呼び出しでは引数リストのかっこの前にドット(.)が必要になる。
Elixirでは関数は第一級のオブジェクトであり、以下のように関数を返す関数/関数を受け取る関数も定義できる。
iex(8)> adder = fn n -> fn m -> n + m end end
#Function<6.52032458/1 in :erl_eval.expr/5>
iex(9)> add_2 = adder.(2)
#Function<6.52032458/1 in :erl_eval.expr/5>
iex(10)> add_2.(3)
5
iex(11)> adder2 = fn n -> n + 2 end
#Function<6.52032458/1 in :erl_eval.expr/5>
iex(12)> func = fn (fun, val) -> fun.(val) end
#Function<12.52032458/2 in :erl_eval.expr/5>
iex(13)> func.(adder2, 3)
5
変数adderに束縛している最初の無名関数は以下と同値である。上ではかっこを省略しているが、以下のようにかっこを明記しても構わない。
adder = fn (n) ->
fn (m) -> n + m end
end
高階関数の概念は、最近ではC#にもよく見られるものなので詳しく説明はしなくともおおよその内容は理解できるはずだ。
無名関数には複数の実装を持たせることができる。
iex(14)> func = fn
...(14)> "hello" -> "goodbye"
...(14)> "goodbye" -> "see you"
...(14)> _ -> "hello"
...(14)> end
この無名関数はパラメーターの値に応じて処理を変える。実際の関数呼び出し時には実引数とパラメーターとの間でパターンマッチが行われ、どのボディーを実行するかが決まる。例えば、「func.("hello")」としたら一番上の節が実行され、結果は「goodbye」になる。アンダースコアは「その他全て」を表す特殊な変数だ。
パラメーターリストにはガード節も記述できる。ガード節というのはwhenキーワードに続けて、何らかの条件式を指定するものだ。以下に例を示す。
iex(15)> func = fn
...(15)> v when is_bitstring(v) -> "string"
...(15)> v when is_number(v) -> "number"
...(15)> _ -> "other"
...(15)> end
ここではis_bitstring/is_numberの2つの関数を利用して、パラメーターの型を調べ、その結果により実行するボディーが決まる。ガード節を使って、関数のどのボディーを実行するかを細かく指定できるので、Elixirでは関数内でifによる条件分岐を行うようなコードは書かずに済むようになっている(あるいは、条件分岐などの制御構造はなるべく使わないようにすることが推奨されている)。
「&」演算子による関数のショートカット記法もある。詳細は割愛するがこれは「&()」内に関数のボディーを記述するものだ。例えば、2つの値を加算する関数は「add = fn v1, v2 -> v1 + v2 end」ではなく、「add = &(&1 + &2)」と記述できる(かっこ内の「&1」と「&2」はパラメーター)。
パイプ
「パイプ」とはUNIXあるいはMS-DOSに慣れた方であれば、よくご存じの概念だ。つまり、Elixirにおけるパイプとは、ある関数の呼び出し結果を別の関数呼び出しにつなぐ処理である。
関数の結果を別の関数に渡そうとすると、「func4(func3(func2(func1(arg))))」のように関数呼び出しがネストしそうになる(そうでなければ、一時的な変数に値を保存して、関数呼び出しを個別に記述していく)。そうではなく、Elixirでは次のような記述が可能だ。
例えば、次のような関数が3つあったとする。
func1 = fn name -> "Hello #{name}, " end
func2 = fn mes -> mes <> "from " end
func3 = fn mes -> mes <> "Elixir" end
あまり意味はない例だが、これはメッセージを順次連結していくことを想定している。なお、func1内の「#{}」は文字列補間を行うもので、func2とfunc3で使われている「<>」は文字列を連結する演算子だ。
これらの関数はもちろん「func3.(func2.(func1.("insider.net")))」のようにも呼び出せるが、パイプ演算子「|>」を使うとよりスッキリと記述できる。
func1.("insider.net") |> func2.() |> func3.()
出力結果:"Hello insider.net, from Elixir"
この例ではあまり使い道が感じられないが、データベースから何らかのデータを取得して、そこから一連の処理を連ねていくといった場合に、簡潔な記述が可能になるだろう。
Elixirは、動的型付けを持つ関数型言語であり、宣言的に関数を記述していくことで簡潔にプログラムを構築できる。本稿ではパターンマッチ、無名関数、パイプ、ガードなど、Elixirを特徴付ける機能を紹介した。本来は各種のデータ型、リスト/タプルの扱い、パッケージマネジャー、複数のプロセスを使うサンプルなども紹介したいところだが、それは別稿に穣る。
Copyright© Digital Advantage Corp. All Rights Reserved.