Elixir 言語仕様ガイド

他言語の経験者が 2〜3 時間でざっと読み通せるリファレンスです。アクターモデル・OTP・関数型設計など Elixir 固有の概念を重点的に解説します。

Elixir 1.17.x経験者向けコード例付き
01

変数・型・不変性

基礎
不変バインディング基礎

Elixir の変数は再代入できない(バインディング)。ただし同名変数へのリバインドは許可される。= は代入ではなく「パターンマッチ演算子」である。

Elixir
x = 42
x = 100      # OK: リバインド(新しいバインディング)
{a, b} = {1, 2}  # パターンマッチで分割代入
^x = 100     # ピン演算子: 値を固定して照合
基本型基礎

Elixir の主な型: 整数・浮動小数点・論理値(true/false はアトム)・アトム(:name)・文字列(バイナリ)・リスト・タプル・マップ。

Elixir
42           # integer
3.14         # float
true         # atom(== :true)
:ok          # atom
"hello"      # binary string(UTF-8)
[1, 2, 3]   # list(linked list)
{:ok, 42}   # tuple
%{a: 1}     # map
アトム基礎

アトムは名前そのものが値であるリテラル。niltruefalse もアトム。関数の戻り値パターン({:ok, val}{:error, reason})に多用される。

Elixir
:ok
:error
:hello
nil == false  # false(nil と false は別のアトム)
is_atom(:ok)  # true
is_nil(nil)   # true
ピン演算子 ^基礎

^x とすると変数をリバインドせず、現在の値でパターンマッチを行う。

Elixir
x = 1
^x = 1   # OK
^x = 2   # ** (MatchError) no match of right hand side value: 2
02

制御フロー

基礎
case 式基礎

case はパターンマッチベースの分岐。when ガードで条件を追加できる。

Elixir
result = {:ok, 42}
case result do
  {:ok, value} when value > 0 -> "positive: #{value}"
  {:ok, value} -> "non-positive: #{value}"
  {:error, reason} -> "error: #{reason}"
end
cond / if / unless基礎

cond は最初の真条件の節を実行する(elif の連鎖に相当)。if/unless は単純な真偽分岐。

Elixir
score = 75
cond do
  score >= 90 -> "A"
  score >= 70 -> "B"
  true -> "C"       # デフォルト節
end

if score >= 60, do: "Pass", else: "Fail"
with 式基礎

with はパターンマッチのチェーン。全て成功した場合のみ do ブロックを実行し、どれかが失敗すると else に流れる。ハッピーパスの記述に使う。

Elixir
with {:ok, user}  <- find_user(id),
     {:ok, token} <- generate_token(user) do
  {:ok, token}
else
  {:error, reason} -> {:error, reason}
end
for 内包表記基礎

for で生成器・フィルタ・変換を組み合わせたコレクション生成。into: で結果のデータ構造を指定できる。

Elixir
evens = for n <- 1..10, rem(n, 2) == 0, do: n * n
# [4, 16, 36, 64, 100]

pairs = for x <- [1,2], y <- [:a,:b], do: {x, y}
# [{1,:a},{1,:b},{2,:a},{2,:b}]
03

関数

基礎
def と defp基礎

def はパブリック関数、defp はプライベート関数(同モジュール内のみ)。関数は必ずモジュール内で定義する。

Elixir
defmodule Math do
  def add(a, b), do: a + b    # パブリック
  defp square(n), do: n * n   # プライベート
  def square_sum(a, b), do: square(a) + square(b)
end

Math.add(3, 4)   # 7
複数節による関数定義基礎

同名・同アリティの関数を複数定義でき、引数のパターンマッチで節が選択される。再帰と組み合わせて使う。

Elixir
defmodule Fib do
  def calc(0), do: 0
  def calc(1), do: 1
  def calc(n) when n > 1, do: calc(n-1) + calc(n-2)
end

Fib.calc(10)  # 55
無名関数基礎

fn ... -> ... end で無名関数を定義。& キャプチャ構文で短く書ける。.(...) で呼び出す。

Elixir
double = fn x -> x * 2 end
double.(5)      # 10

double2 = &(&1 * 2)    # キャプチャ構文
double2.(5)     # 10

add = &(&1 + &2)
add.(3, 4)      # 7
パイプ演算子 |>基礎

|> で関数を左から右へチェーン。左辺の値が次の関数の第一引数になる。読みやすい変換パイプラインを構築できる。

Elixir
"  hello world  "
|> String.trim()
|> String.split(" ")
|> Enum.map(&String.capitalize/1)
|> Enum.join(" ")
# "Hello World"
04

コレクション

基礎
List基礎

連結リスト。先頭への追加が O(1)、末尾は O(n)。| でパターン分割。Enum モジュールで操作。

Elixir
list = [1, 2, 3]
[h | t] = list    # h=1, t=[2,3]
new_list = [0 | list]  # [0,1,2,3]
Enum.map(list, &(&1 * 2))    # [2,4,6]
Enum.filter(list, &(&1 > 1)) # [2,3]
Enum.reduce(list, 0, &+/2)   # 6
Tuple基礎

固定長・高速アクセスのデータ構造。関数の戻り値に {:ok, val} パターンで使われる。要素の変更は新しいタプルを生成する。

Elixir
t = {:ok, "hello", 42}
elem(t, 1)              # "hello"
put_elem(t, 2, 99)      # {:ok, "hello", 99}
tuple_size(t)           # 3
Map基礎

キーバリューストア。アトムキーには map.key でアクセス可能。Map.putMap.update で変更(新しい Map を返す)。

Elixir
m = %{name: "Alice", age: 30}
m.name                          # "Alice"
Map.get(m, :age, 0)             # 30
m2 = Map.put(m, :age, 31)       # 新しい Map
m3 = %{m | age: 31}             # 更新構文(既存キーのみ)
Keyword List基礎

[key: value] 形式のタプルリスト。順序を保持し、重複キーも持てる。関数オプションや DSL によく使われる。

Elixir
opts = [timeout: 5000, retry: 3]
Keyword.get(opts, :timeout)   # 5000

# 関数に渡す場合(末尾の [] は省略可)
String.split("a,b,c", ",", trim: true)
05

文字列・バイナリ

基礎
文字列(バイナリ)基礎

Elixir の文字列は UTF-8 エンコードのバイナリ。"..." で文字列補間(#{expr})が使える。~s シギルや三重クォート(""")もある。

Elixir
name = "Elixir"
"Hello, #{name}!"          # 文字列補間
String.length("hello")    # 5
String.upcase("hello")    # "HELLO"
String.split("a,b", ",")  # ["a","b"]
チャーリストとシギル基礎

シングルクォートの '...' はチャーリスト(コードポイントのリスト)。シギル(~r~w~c 等)で特殊なリテラルを表現できる。

Elixir
'hello'   # チャーリスト([104,101,108,108,111])
~w[foo bar baz]   # ["foo","bar","baz"]
~r/elixir/i       # 正規表現
~c[hello]         # チャーリスト(新記法)
バイナリパターンマッチ応用

<<>> でバイナリのビット/バイトレベルのパターンマッチが可能。ネットワークプロトコルやファイルパーサーに使われる。

Elixir
<<r::8, g::8, b::8>> = <<255, 128, 0>>
# r=255, g=128, b=0

<<first::binary-size(3), rest::binary>> = "Hello"
# first="Hel", rest="lo"
06

エラー処理

基礎
{:ok, val} / {:error, reason} パターン基礎

Elixir の慣習的なエラーハンドリング。関数は {:ok, value} または {:error, reason} を返し、呼び出し元がパターンマッチで処理する。

Elixir
case File.read("data.txt") do
  {:ok, content}   -> process(content)
  {:error, :enoent} -> IO.puts("File not found")
  {:error, reason} -> IO.puts("Error: #{reason}")
end
try/rescue/catch基礎

例外(Exception)と throw の両方を捕捉できる。rescue は例外、catchthrow/exit/:EXIT。通常は {:ok, _} パターンを優先する。

Elixir
try do
  String.to_integer("abc")
rescue
  e in ArgumentError -> "Invalid: #{e.message}"
after
  IO.puts("cleanup")  # 必ず実行
end
バン関数(!)基礎

func! の命名規則の関数は失敗時に例外を発生させる。成功値を直接返すため、パターンマッチが不要な場面で使う。

Elixir
# 通常版({:ok, _} / {:error, _} を返す)
{:ok, content} = File.read("data.txt")

# バン版(成功時は内容を返し、失敗は例外)
content = File.read!("data.txt")
07

プロセスとメッセージパッシング

基礎
spawn とプロセス基礎

Elixir のプロセスは BEAM VM 上の軽量プロセス(スレッドではない)。数百万個同時に起動できる。spawn/1 で新プロセスを起動し PID で識別。

Elixir
pid = spawn(fn -> IO.puts("Hello from process!") end)
Process.alive?(pid)   # false(すぐ終了)

self()   # 現在のプロセスの PID
Process.info(self(), :memory)
send / receive基礎

プロセス間通信はメッセージパッシングのみ。send/2 でメッセージ送信、receive でメールボックスから取り出す。

Elixir
parent = self()
pid = spawn(fn ->
  receive do
    {:hello, from} -> send(from, {:reply, "Hi!"})
  end
end)

send(pid, {:hello, parent})
receive do
  {:reply, msg} -> IO.puts(msg)  # "Hi!"
end
link と monitor基礎

spawn_link/1 でリンクしたプロセスは一方がクラッシュするともう一方も終了(フォールトトレランス)。spawn_monitor/1 はクラッシュを通知として受け取れる。

Elixir
# モニター(片方向の監視)
{pid, ref} = spawn_monitor(fn -> raise "oops" end)
receive do
  {:DOWN, ^ref, :process, _pid, reason} ->
    IO.puts("Died: #{inspect(reason)}")
end
Agent と Task基礎

Agent は状態を保持するプロセスの抽象。Task は非同期タスクの実行と結果取得(Task.asyncTask.await)のための高レベル API。

Elixir
# Task で並行処理
t1 = Task.async(fn -> :timer.sleep(100); 1 end)
t2 = Task.async(fn -> :timer.sleep(100); 2 end)
[Task.await(t1), Task.await(t2)]  # [1, 2]

# Agent で状態管理
{:ok, agent} = Agent.start_link(fn -> 0 end)
Agent.update(agent, &(&1 + 1))
Agent.get(agent, & &1)   # 1
08

GenServer・OTP

基礎
GenServer基礎

GenServer は OTP の汎用サーバービヘイビア。call(同期)と cast(非同期)でメッセージ送受信し、handle_call/handle_cast でコールバック処理する。

Elixir
defmodule Counter do
  use GenServer

  def start_link(init), do: GenServer.start_link(__MODULE__, init, name: __MODULE__)
  def increment, do: GenServer.cast(__MODULE__, :inc)
  def value, do: GenServer.call(__MODULE__, :get)

  @impl true
  def init(n), do: {:ok, n}
  @impl true
  def handle_cast(:inc, n), do: {:noreply, n + 1}
  @impl true
  def handle_call(:get, _from, n), do: {:reply, n, n}
end
Supervisor基礎

Supervisor は子プロセスの起動・再起動を管理。strategy: :one_for_one(1つがクラッシュしたら1つ再起動)などで障害耐性を設計する。

Elixir
defmodule MyApp.Supervisor do
  use Supervisor

  def start_link(_), do: Supervisor.start_link(__MODULE__, :ok, name: __MODULE__)

  @impl true
  def init(:ok) do
    children = [
      {Counter, 0},
      {MyWorker, []}
    ]
    Supervisor.init(children, strategy: :one_for_one)
  end
end
OTP アプリケーション応用

OTP アプリケーションは起動時にスーパービジョンツリーを立ち上げる。mix.exs でアプリケーション設定し、Application.start/2 をコールバックに実装する。

Elixir
defmodule MyApp.Application do
  use Application

  @impl true
  def start(_type, _args) do
    children = [MyApp.Supervisor]
    opts = [strategy: :one_for_one, name: MyApp.Supervisor]
    Supervisor.start_link(children, opts)
  end
end
09

Phoenix フレームワーク概要

応用
Phoenix とは応用

Elixir 最大の Web フレームワーク。高並行性・低レイテンシが特徴。MVC + Channel(WebSocket)+ LiveView(リアルタイム UI)を提供。

Elixir
# mix.exs
{:phoenix, "~> 1.7"},
{:phoenix_live_view, "~> 0.20"}

# $ mix phx.new my_app
# $ cd my_app && mix phx.server
Router・Controller・View応用

Router でパスを定義 → Controller でリクエスト処理 → View でレスポンス生成。Plug はミドルウェアの仕組みで、パイプラインを構成できる。

Elixir
# router.ex
scope "/api", MyAppWeb do
  pipe_through :api
  get "/users", UserController, :index
  post "/users", UserController, :create
end

# user_controller.ex
def index(conn, _params) do
  users = Accounts.list_users()
  json(conn, users)
end
Ecto(DB ライブラリ)応用

Ecto は ORM ではなくデータマッピングとクエリビルダー。SchemaChangeset(バリデーション)・Repo(DB 操作)・クエリ DSL を提供。

Elixir
defmodule User do
  use Ecto.Schema
  schema "users" do
    field :name, :string
    field :email, :string
    timestamps()
  end
  def changeset(user, attrs) do
    user |> cast(attrs, [:name, :email])
         |> validate_required([:name, :email])
  end
end

Repo.all(from u in User, where: u.name == "Alice")
10

列挙・Stream

基礎
Enum モジュール基礎

Enum はコレクション操作の標準ライブラリ。即時評価(全要素処理してから次へ)。

Elixir
Enum.map([1,2,3], &(&1 * 2))         # [2,4,6]
Enum.filter([1,2,3,4], &rem(&1,2)==0) # [2,4]
Enum.reduce([1,2,3], 0, &+/2)         # 6
Enum.sort_by(users, & &1.name)
Enum.group_by(users, & &1.role)
Stream(遅延評価)基礎

Stream は遅延評価版の Enum。大量データや無限シーケンスの効率的な処理に使う。最終的に Enum.to_list などで評価する。

Elixir
1..1_000_000
|> Stream.filter(&rem(&1, 2) == 0)
|> Stream.map(&(&1 * &1))
|> Enum.take(5)
# [4, 16, 36, 64, 100](必要な分だけ計算)
for 内包表記(高度)基礎

into: でマップに集約したり、reduce: で任意の初期値に畳み込める。

Elixir
# Map を生成
word_lengths = for word <- ~w[hello world], into: %{} do
  {word, String.length(word)}
end
# %{"hello" => 5, "world" => 5}
11

モジュール・メタプログラミング

応用
モジュールとアトリビュート基礎

モジュールはコードの名前空間。@moduledoc@doc でドキュメント、@spec で型仕様を記述。@ アトリビュートはコンパイル時定数としても使える。

Elixir
defmodule MyMath do
  @moduledoc "数学ユーティリティ"
  @pi 3.14159265

  @spec circle_area(number) :: float
  @doc "円の面積を計算"
  def circle_area(r), do: @pi * r * r
end
マクロ(defmacro)応用

defmacro でコンパイル時にコードを生成するマクロを定義。quote/unquote で AST(抽象構文木)を操作する。

Elixir
defmodule MyMacro do
  defmacro unless(condition, do: body) do
    quote do
      if !unquote(condition), do: unquote(body)
    end
  end
end

import MyMacro
unless false, do: IO.puts("executed!")
behaviour(ビヘイビア)応用

ビヘイビアはモジュールが実装すべきコールバック関数の仕様を定義する仕組み(インターフェースに相当)。OTP の GenServer もビヘイビア。

Elixir
defmodule Storage do
  @callback save(key :: String.t, value :: term) :: :ok | {:error, term}
  @callback get(key :: String.t) :: {:ok, term} | :error
end

defmodule MemStorage do
  @behaviour Storage
  @impl true
  def save(k, v), do: :ets.insert(:store, {k, v}); :ok
  @impl true
  def get(k), do: case :ets.lookup(:store, k) do
    [{^k, v}] -> {:ok, v}; [] -> :error
  end
end
12

ファイル・IO

基礎
File モジュール基礎

File モジュールでファイルの読み書き。File.stream! で大きなファイルを効率的にストリーム処理できる。

Elixir
File.write!("hello.txt", "Hello, World!")
content = File.read!("hello.txt")    # "Hello, World!"

File.stream!("large.csv")
|> Stream.map(&String.trim/1)
|> Enum.each(&process/1)
IO モジュール基礎

標準入出力。IO.puts(改行あり出力)・IO.write(改行なし)・IO.gets(入力)・IO.inspect(デバッグ表示)。

Elixir
IO.puts("Hello!")
IO.write("no newline")
name = IO.gets("Name: ") |> String.trim()

# パイプライン内デバッグに便利
[1, 2, 3]
|> IO.inspect(label: "before")
|> Enum.map(&(&1 * 2))
|> IO.inspect(label: "after")
Path モジュール基礎

ファイルパス操作のユーティリティ。OS を意識せずクロスプラットフォームなパス操作ができる。

Elixir
Path.join(["home", "user", "file.txt"])  # "home/user/file.txt"
Path.expand("~/data")                   # "/Users/user/data"
Path.extname("app.ex")                  # ".ex"
Path.dirname("/home/user/file.txt")     # "/home/user"
13

型仕様・Dialyzer

応用
@spec・@type応用

@spec で関数の型仕様を記述。Dialyzer(静的解析ツール)が型不整合を検出する。Elixir は動的型付けだが型仕様でドキュメント化と解析が可能。

Elixir
@type user :: %{name: String.t(), age: non_neg_integer()}

@spec greet(String.t()) :: String.t()
def greet(name), do: "Hello, #{name}!"

@spec divide(number(), number()) :: {:ok, float()} | {:error, :division_by_zero}
def divide(_, 0), do: {:error, :division_by_zero}
def divide(a, b), do: {:ok, a / b}
Dialyzer / Dialyxir応用

dialyxir は Mix プロジェクトで Dialyzer を使いやすくするツール。PLT(Persistent Lookup Table)のビルドに時間がかかるが、型エラーを事前に検出できる。

Elixir
# mix.exs
{:dialyxir, "~> 1.0", only: [:dev], runtime: false}

# $ mix dialyzer   # PLT 構築 + 解析
ガード節基礎

ガード節(when)で使える式は限定的(副作用なし)。is_integeris_list・算術・比較・and/or/not など Kernel モジュールの関数が使える。

Elixir
def classify(x) when is_integer(x) and x > 0, do: :positive
def classify(x) when is_integer(x) and x < 0, do: :negative
def classify(0), do: :zero
def classify(_), do: :not_a_number
14

Mix・エコシステム

基礎
Mix ビルドツール基礎

mix は Elixir 標準のビルドツール。プロジェクト作成・コンパイル・テスト・依存管理を一元化。

Elixir
# 新規プロジェクト
mix new my_app
mix new my_app --sup   # スーパービジョンツリー付き

# よく使うコマンド
mix compile     # コンパイル
mix test        # テスト
mix format      # フォーマット
mix deps.get    # 依存解決
Hex パッケージマネージャー基礎

hex.pm は Elixir/Erlang のパッケージリポジトリ。mix.exsdeps/0 で依存を宣言し mix deps.get で取得。

Elixir
# mix.exs
defp deps do
  [
    {:phoenix, "~> 1.7"},
    {:ecto_sql, "~> 3.10"},
    {:jason, "~> 1.4"},
    {:ex_doc, "~> 0.30", only: :dev}
  ]
end
ExUnit テストフレームワーク基礎

組み込みのテストフレームワーク。assertrefuteassert_raise でアサーション。describesetup でテストの構造化。

Elixir
defmodule MathTest do
  use ExUnit.Case, async: true

  describe "add/2" do
    test "adds two numbers" do
      assert Math.add(1, 2) == 3
    end
    test "handles negatives" do
      assert Math.add(-1, 1) == 0
    end
  end
end
15

分散処理・クラスタリング

応用
分散 Erlang ノード応用

BEAM VM は分散ノード間の透過的なメッセージパッシングをサポート。Node.connect/1 でノードを接続し、他ノードのプロセスに send できる。

Elixir
# ノード起動: iex --sname node1@localhost
Node.connect(:"node2@localhost")
Node.list()   # 接続ノード一覧

# 他ノードにメッセージ送信
send({:my_process, :"node2@localhost"}, {:hello, self()})
Horde / libcluster応用

libcluster はノード自動検出ライブラリ。Horde は分散スーパービジョンとレジストリを提供し、クラスター全体でのプロセス管理を可能にする。

Elixir
# libcluster 設定例
config :libcluster,
  topologies: [
    k8s: [
      strategy: Cluster.Strategy.Kubernetes,
      config: [kubernetes_selector: "app=my_app"]
    ]
  ]
ETS / Mnesia応用

ETS(Erlang Term Storage)は高速なインメモリストレージ(テーブル)。Mnesia は分散インメモリデータベース。どちらも Erlang/OTP の標準機能。

Elixir
:ets.new(:cache, [:named_table, :public])
:ets.insert(:cache, {"key", "value"})
:ets.lookup(:cache, "key")   # [{"key","value"}]
:ets.delete(:cache, "key")
16

パフォーマンス・デバッグ

応用
Observer / iex応用

:observer.start() で BEAM の GUI モニタリングツールを起動。プロセスツリー・メモリ・メッセージキューをリアルタイム観察できる。

Elixir
# iex で起動
:observer.start()

# プロセス情報
Process.info(pid)
Process.list() |> length()   # 全プロセス数

# メッセージキュー確認
Process.info(pid, :message_queue_len)
Benchee ベンチマーク応用

benchee は Elixir のベンチマークライブラリ。複数の実装を比較し、ops/sec・メモリ使用量・分布を計測できる。

Elixir
Benchee.run(%{
  "Enum.map" => fn -> Enum.map(1..1000, &(&1 * 2)) end,
  "for"      => fn -> for n <- 1..1000, do: n * 2 end
})
recon / 本番デバッグ応用

recon は本番環境のデバッグツールセット。recon_trace でトレーシング、recon:proc_count でプロセスのリソース消費ランキングを確認できる。

Elixir
# 本番での高いメモリ使用プロセス調査
:recon.proc_count(:memory, 10)

# 関数呼び出しトレース
:recon_trace.calls({MyModule, :my_fun, :_}, 100)
パフォーマンスのコツ応用

文字列の繰り返し連結には IO List を使う。大量のデータは Stream で遅延処理。末尾再帰で深い再帰を最適化。

Elixir
# 悪い例: 文字列を都度連結(O(n^2))
Enum.reduce(list, "", fn s, acc -> acc <> s end)

# 良い例: IO リストで最後に join(O(n))
IO.iodata_to_binary(Enum.intersperse(list, ""))
# または
Enum.join(list, "")
🏠