コンテンツにスキップ

Elixir

出典: フリー教科書『ウィキブックス(Wikibooks)』

本書は、プログラミング言語 Elixir の教科書です。 Elixirは、Erlang VM上で動作する関数型プログラミング言語であり、拡張性、耐久性、並列性に優れています。 Elixirは、Erlangの文法を基に構築されているため、分散システム、並行処理、高可用性のアプリケーション開発に適しています。 本書では、Elixirの基本的な機能から、リアルタイムシステム、大規模システムの開発、および高度なトピックまでをカバーしています。 また本書では、実用的なアプリケーションの開発に必要なElixirの様々な機能を説明しています。

Elixirの概要

[編集]

Elixirは、Erlang仮想マシン(BEAM)で実行される動的な関数型プログラミング言語です。Elixirは、RubyやClojureなどの言語の影響を受けており、構文はRubyに似ています。

Elixirは、並行処理や分散処理に最適化されており、Erlangが持つ強力なコンカレンシーモデルを利用しています。Elixirは、プロセス指向プログラミングをサポートし、プロセス間通信を簡単に実現することができます。また、Elixirは、並行処理を実現するためのツールとして、並行性の高いマルチコア処理を実現する「OTP(Open Telecom Platform)」を提供しています。

Elixirは、Web開発、マイクロサービス、ネットワークプログラミング、データ処理など、多様な用途に利用されています。また、Elixirは、PhoenixというWebフレームワークを持っており、高速かつスケーラブルなWebアプリケーションを開発するための強力なツールとなっています。

Elixirの特徴として、柔軟な構文、優れた拡張性、高いパフォーマンス、堅牢なエラーハンドリング、並行処理を実現する機能が挙げられます。Elixirは、コンパイルしてBEAM上で実行することができますが、REPL(Read-Eval-Print Loop)を利用して、インタラクティブな開発も可能です。Elixirは、関数型プログラミングのパラダイムに基づいており、不変性と副作用の排除を重視しています。

Elixirは、オープンソースであり、活発なコミュニティによって支えられています。Elixirの開発者は、Erlangの優れた機能を生かしながら、プログラマブルな言語を提供することで、現代的なアプリケーション開発に対応しています。

Elixirの基本

[編集]
Elixirの文法の概観
次のコードはElixirの基本的な概念と構文を説明しています。
#データ型
# 整数
1

# 浮動小数点数
3.14

# 論理値
true
false

# アトム
:atom

# 文字列
"Hello, World!"

# リスト
[1, 2, 3]

# タプル
{1, 2, 3}

# マップ
%{key: "value"}

#変数と代入
# 変数の定義
x = 1

# 変数への代入
x = 2

# 複数の変数への代入
{x, y} = {1, 2}

#関数定義
# 関数定義
def hello(name) do
  "Hello, #{name}!"
end

# 引数なしの関数定義
def hello do
  "Hello, World!"
end

#条件分岐
# if文
if x > 0 do
  "positive"
else
  "non-positive"
end

# case文
case x do
  1 -> "one"
  2 -> "two"
  _ -> "other"
end

#モジュールと関数の呼び出し
# モジュールの定義
defmodule MyModule do
  def my_function do
    "Hello from MyModule!"
  end
end

# モジュールの関数の呼び出し
MyModule.my_function()

# 別名での関数呼び出し
alias MyModule
MyModule.my_function()

# パイプ演算子を使った関数呼び出し
"Hello" |> String.upcase() |> IO.puts()

#Enum
# Enumモジュールによるリスト処理
list = [1, 2, 3]
Enum.each(list, fn x -> IO.puts(x) end)
Enum.map(list, fn x -> x * 2 end)
Enum.filter(list, fn x -> rem(x, 2) == 0 end)
Enum.reduce(list, 0, fn x, acc -> x + acc end)

#プロセスと並行性
# プロセスの生成
pid = spawn(fn -> IO.puts("Hello from process #{inspect(self())}") end)

# プロセス間のメッセージ送信
send(pid, "Hello from main process")

# メッセージの受信
receive do
  message -> IO.puts("Received message: #{inspect(message)}")
end

#ファイル操作
# ファイルの読み書き
{:ok, file} = File.open("file.txt", [:write])
IO.write(file, "Hello, World!")
File.close(file)
{:ok, contents}
変数と型
Elixirは動的型付け言語であり、変数の型は宣言する必要はありません。変数にはAtom、整数、浮動小数点数、文字列、タプル、リスト、マップなど、多くの型があります。
x = 1
y = 2
z = x + y
IO.puts z
関数
関数はElixirの基本的な要素であり、defキーワードを使用して定義されます。関数名、引数、戻り値、関数本体のブロックから構成されます。
defmodule Math do
  def add(a, b) do
    a + b
  end
end

IO.puts Math.add(1, 2)
パターンマッチング
Elixirでは、パターンマッチングが重要な役割を担っています。パターンマッチングは、ある値が特定のパターンにマッチするかどうかを評価する方法です。Elixirでは、関数の引数にパターンマッチングを使用することができます。
def my_function("hello") do
  # do something
end

def my_function("world") do
  # do something else
end

my_function("hello")
my_function("world")
モジュールとコードの組織化
Elixirでは、モジュールを使用して関数をグループ化し、コードを組織化します。モジュールには、関数の他にも、状態を保持するためのステート変数や、モジュール自体を制御するためのコールバック関数などがあります。
defmodule MyModule do
  def my_function(arg1, arg2) do
    # function body
  end

  def my_other_function(arg1) do
    # function body
  end
end
並列処理
Elixirは、Erlang VMのおかげで、複数のプロセスを実行することができます。プロセスは、独立して動作する軽量なスレッドのようなもので、メッセージパッシングを使用して通信します。Elixirでは、spawnやspawn_linkなどの関数を使用して、新しいプロセスを作成することができます。
pid = spawn(fn -> my_function() end)
パイプ演算子
パイプ演算子「|>」を使用することで、関数の結果を別の関数の引数として渡すことができます。以下の例では、文字列を大文字に変換してから、IO.putsで出力しています。
"hello world"
|> String.upcase()
|> IO.puts()

関数とモジュール

[編集]

関数

[編集]

関数は、defキーワードを使用して定義できます。以下は、2つの整数を加算する関数の例です。

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

IO.puts Math.add(2, 3) #=> 5
defmoduleキーワードを使用してMathモジュールを定義しています。defキーワードを使用して、add関数をMathモジュールに定義しています。add関数は、2つの引数を受け取り、それらを加算して返します。

モジュール

[編集]

モジュールは、defmoduleキーワードを使用して定義できます。以下は、モジュール内に複数の関数を定義する例です。

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

  def subtract(a, b) do
    a - b
  end
end

IO.puts Math.add(2, 3) #=> 5
IO.puts Math.subtract(5, 2) #=> 3
Mathモジュールにadd関数とsubtract関数を定義しています。add関数は、2つの引数を加算して返し、subtract関数は、2つの引数を減算して返します。

モジュールは、requireキーワードを使用して他のモジュールをインポートすることができます。以下は、Enumモジュールをインポートして、Enum.map関数を使用する例です。

defmodule MyList do
  require Enum

  def double_list(list) do
    Enum.map(list, &(&1 * 2))
  end
end

IO.inspect MyList.double_list([1, 2, 3]) #=> [2, 4, 6]
MyListモジュールにdouble_list関数を定義しています。requireキーワードを使用して、Enumモジュールをインポートし、Enum.map関数を使用して、リスト内のすべての要素を2倍にします。IO.inspect関数を使用して、MyList.double_list([1, 2, 3])の出力を表示しています。

パターンマッチング

[編集]

Elixirは、パターンマッチングに重点を置いた関数型言語です。パターンマッチングは、変数に特定のパターンに一致する値を割り当てることで、コードをシンプルで明確にすることができます。以下は、Elixirのパターンマッチングの例です。

この例では、Exampleモジュールのgreet/1関数が定義されています。この関数は、引数として与えられたパターンに基づいて、異なる値を返します。

最初の2つの関数は、:helloおよび:goodbyeというアトムを引数として受け取ります。これらのアトムが渡された場合、それぞれ"Hello, World!"および"Goodbye, World!"という文字列を返します。

最後の関数は、nameという引数を取ります。この関数は、:helloまたは:goodbye以外の引数が与えられた場合に実行されます。この場合、引数を名前として使用して、"Hello, #{name}!"という文字列を返します。

defmodule Example do
  def greet(:hello), do: "Hello, World!"
  def greet(:goodbye), do: "Goodbye, World!"
  def greet(name), do: "Hello, #{name}!"
end

IO.puts Example.greet(:hello)  # => "Hello, World!"
IO.puts Example.greet(:goodbye)  # => "Goodbye, World!"
IO.puts Example.greet("Alice")  # => "Hello, Alice!"

この例では、パターンマッチングを使用して、引数に基づいて異なる結果を返す関数を定義する方法を示しています。Elixirでは、パターンマッチングをより複雑なデータ構造にも適用することができます。例えば、リスト、タプル、マップなどのデータ構造に対してもパターンマッチングを行うことができます。

主要なデータ構造

[編集]

Elixirは、リスト、タプル、マップ、関連リストおよびセットという3つの主要なデータ構造を提供しています。以下に、それぞれのデータ構造のコード例を示します。

リスト

[編集]

リストは、Elixirで最も一般的なデータ構造の1つであり、要素を順序付きで格納します。

# リストの作成
list = [1, 2, 3, 4, 5]

# 要素の追加
list = [0 | list]

# 要素の削除
list = List.delete(list, 3)

# 要素の取得
first = hd(list)
last = List.last(list)
second = Enum.at(list, 1)
最初にリストを作成し、その後要素を追加、削除、取得する方法を示しています。

タプル

[編集]

タプルは、Elixirで不変のデータ構造であり、複数の要素をグループ化します。タプルは、フィールドの数と型が固定されているため、静的な構造を持ちます。

# タプルの作成
tuple = {:ok, "success"}

# 要素の取得
first = elem(tuple, 0)
second = elem(tuple, 1)

# タプルを更新
tuple = put_elem(tuple, 1, "updated")
最初にタプルを作成し、その後要素を取得および更新する方法を示しています。

マップ

[編集]

マップは、キーと値のペアを持つ、Elixirで最も柔軟なデータ構造の1つです。マップは、キーを使用してアクセスできるため、データの検索に非常に便利です。

# マップの作成
map = %{"key1" => "value1", "key2" => "value2"}

# 要素の追加
map = Map.put(map, "key3", "value3")

# 要素の削除
map = Map.delete(map, "key1")

# 要素の取得
value = Map.get(map, "key2")
最初にマップを作成し、その後要素を追加、削除、取得する方法を示しています。

関連リスト

[編集]

関連リスト(Associative List; 連想配列) は、キーと値のペアを持つリストです。キーと値のペアは、タプルの形式で表します。例えば、以下のような関連リストを定義できます。

iex> list = [{:a, 1}, {:b, 2}, {:c, 3}]
[{:a, 1}, {:b, 2}, {:c, 3}]

セット

[編集]

セット(Set; 集合) は、重複のない要素のコレクションです。セットは、Setモジュールを使用して作成し、Set.new/0関数を使用して空のセットを作成できます。例えば、以下のようなセットを作成できます。

iex> set = Set.new([1, 2, 3])
#Set<[1, 2, 3]>

制御構造

[編集]

Elixirは、パターンマッチング、再帰、そして関数型プログラミングのコンセプトに基づいて、多様な制御構造を提供しています。以下に、いくつかの制御構造とそのコード例を示します。

  • 条件分岐 - if/else

if/elseは、条件に基づいて分岐するために使用されます。

if x > 0 do
  IO.puts("x is positive")
else
  IO.puts("x is not positive")
end
  • 繰り返し - for

forは、要素のリストまたは範囲内で繰り返しを行うために使用されます。

for i <- [1, 2, 3], do: IO.puts(i)
  • 再帰 - recursion

再帰は、関数が自分自身を呼び出すことによって、繰り返し処理を実現するために使用されます。

defmodule Math do
  def sum_list([]), do: 0
  def sum_list([head | tail]), do: head + sum_list(tail)
end

Math.sum_list([1, 2, 3, 4]) #=> 10
  • ガード節 - guard clauses

ガード節は、関数呼び出しの前に条件式を評価して、特定の引数を拒否することができます。

defmodule MyModule do
  def my_func(x) when x > 0, do: IO.puts("x is positive")
  def my_func(x) when x == 0, do: IO.puts("x is zero")
  def my_func(x) when x < 0, do: IO.puts("x is negative")
end

MyModule.my_func(5) #=> "x is positive"
  • パイプライン演算子 - pipe operator

パイプライン演算子は、複数の関数呼び出しを繋げて、関数の結果を別の関数に渡すことができます。

"Hello, World!"
|> String.upcase()
|> IO.puts()

これらは、Elixirで使用できる一般的な制御構造の例です。Elixirには、パターンマッチング、例外処理、そしてマクロなどの制御構造もあります。

並列処理と並行性

[編集]

プロセス

[編集]

OTP

[編集]

Webフレームワーク

[編集]

ElixirにはいくつかのWebフレームワークがあります。最も人気のあるフレームワークは、以下のとおりです。

  • Phoenix: Phoenixは、Elixirで書かれた最も人気のあるWebフレームワークです。Phoenixは、高速でスケーラブルなWebアプリケーションを開発するために設計されています。Phoenixは、WebSocketsやリアルタイム通信などの機能も提供しています。
  • Nerves: Nervesは、ElixirでIoT(Internet of Things)アプリケーションを開発するためのフレームワークです。Nervesは、ElixirとErlangのライブラリを使用して、組み込みデバイスでのアプリケーション開発をサポートしています。
  • Sugar: Sugarは、Elixirの軽量Webフレームワークです。Sugarは、Phoenixよりもシンプルで、より小規模なアプリケーションの開発に適しています。
  • Plug: Plugは、ElixirのWebアプリケーションの中間層フレームワークです。Plugは、HTTPリクエストとレスポンスを処理するための一連のプラグインを提供し、カスタムWebアプリケーションを構築するためのフレームワークとして使用されます。

これらのフレームワークの中で、Phoenixが最も人気があり、最も多くの機能を提供しています。しかし、アプリケーションの要件に応じて、他のフレームワークを使用することもできます。

テスト

[編集]

デプロイ

[編集]

Elixirのコードの最適化

[編集]

Elixirのコードを最適化するためには、以下のような手法があります。

  1. プロファイリングを行う まずは、プロファイリングツールを使ってアプリケーションのボトルネックを特定しましょう。Elixirには、:observer:reconなどのプロファイリングツールが用意されています。
  2. 遅延評価を利用する Elixirには、遅延評価を利用できるStreamEnumといったモジュールがあります。これらのモジュールを使うことで、メモリ使用量を減らし、パフォーマンスを向上させることができます。
  3. プロセスを利用する Elixirは、プロセス指向の言語であり、並行処理を強くサポートしています。適切にプロセスを利用することで、処理を並列化することができ、パフォーマンスを向上させることができます。
  4. ネイティブコードへのコンパイル Elixirは、Erlang VM上で動作する言語ですが、:erlang.compile/2関数を使うことで、ネイティブコードにコンパイルすることができます。ネイティブコードにすることで、パフォーマンスが向上することがあります。
  5. JITコンパイルを利用する Elixir 1.12からは、Just-In-Time(JIT)コンパイルがサポートされています。JITコンパイルを使うことで、実行時に最適化されたコードを生成することができ、パフォーマンスが向上することがあります。
  6. ライブラリの選択 Elixirには、さまざまなライブラリがありますが、パフォーマンスを優先する場合は、ベンチマークやプロファイリングの結果を参考に、最適なライブラリを選択することが重要です。
  7. データ構造の選択 Elixirには、リスト、マップ、キーワードリスト、タプルなどのデータ構造がありますが、処理に応じて適切なデータ構造を選択することが重要です。たとえば、リストは要素の追加がO(n)のため、要素の追加が頻繁に行われる場合は、別のデータ構造を選択することが望ましい。
プロセス指向の言語とは
プロセス指向の言語は、コンピュータの処理をプロセス単位で制御するために設計されたプログラミング言語のことです。プロセス指向の言語では、プログラムを複数のプロセスに分割し、それぞれのプロセスが独立して実行されます。

プロセス指向の言語では、プロセス間通信 (IPC) が重要な役割を果たします。プロセスはデータを共有する必要がある場合がありますが、それを実現するためには、IPC メカニズムを使用してデータを転送する必要があります。代表的なプロセス指向の言語としては、C言語、FORTRAN、COBOLなどがあります。

一方、オブジェクト指向の言語では、プログラムをオブジェクト単位で分割し、それぞれのオブジェクトが独立して実行されます。オブジェクト指向の言語では、オブジェクト間のメッセージパッシングが重要な役割を果たします。代表的なオブジェクト指向の言語としては、Java、C++、Pythonなどがあります。