Skip to content

パターンマッチング完全ガイド

パターンマッチング完全ガイド

Section titled “パターンマッチング完全ガイド”

Elixirのパターンマッチング機能を、実務で使える実装例とともに詳しく解説します。

パターンマッチングは、Elixirの最も重要な機能の一つです。データの構造を分解し、条件分岐を簡潔に記述できます。

パターンマッチングの用途
├─ データの分解
├─ 条件分岐
├─ 関数の多重定義
└─ エラーハンドリング

なぜパターンマッチングが必要か

Section titled “なぜパターンマッチングが必要か”

問題のある構成(パターンマッチングなし):

# 問題: 複雑な条件分岐
def process_result(result) do
if is_tuple(result) do
if tuple_size(result) == 2 do
status = elem(result, 0)
data = elem(result, 1)
if status == :ok do
handle_success(data)
else
handle_error(data)
end
end
end
end

解決: パターンマッチングによる簡潔な記述

# 解決: パターンマッチングで簡潔に
def process_result({:ok, data}) do
handle_success(data)
end
def process_result({:error, reason}) do
handle_error(reason)
end

2. 基本的なパターンマッチング

Section titled “2. 基本的なパターンマッチング”
# 基本的な代入
x = 1
# タプルのパターンマッチング
{a, b} = {1, 2}
# a => 1, b => 2
# リストのパターンマッチング
[head | tail] = [1, 2, 3, 4]
# head => 1, tail => [2, 3, 4]
# マップのパターンマッチング
%{name: name, age: age} = %{name: "Alice", age: 30}
# name => "Alice", age => 30
# 変数の値を固定(再代入を防ぐ)
x = 1
^x = 1 # 成功
^x = 2 # MatchError
# パターンマッチングでの使用
x = 1
{^x, y} = {1, 2} # 成功: xは1で固定
{^x, y} = {2, 3} # MatchError: xは1なので2とマッチしない

3. 関数でのパターンマッチング

Section titled “3. 関数でのパターンマッチング”
defmodule Math do
# 0の場合
def factorial(0), do: 1
# 正の数の場合
def factorial(n) when n > 0 do
n * factorial(n - 1)
end
# 負の数の場合
def factorial(_n) do
{:error, "Factorial is not defined for negative numbers"}
end
end
Math.factorial(5) # => 120
Math.factorial(0) # => 1
Math.factorial(-1) # => {:error, "Factorial is not defined for negative numbers"}
defmodule User do
def process_user(%{age: age}) when age >= 18 do
{:ok, "Adult user"}
end
def process_user(%{age: age}) when age < 18 do
{:ok, "Minor user"}
end
def process_user(_) do
{:error, "Invalid user"}
end
end

4. case文でのパターンマッチング

Section titled “4. case文でのパターンマッチング”
case File.read("config.json") do
{:ok, content} ->
IO.puts("File read successfully")
process_content(content)
{:error, :enoent} ->
IO.puts("File not found")
{:error, reason} ->
IO.puts("Error: #{inspect(reason)}")
end
case {1, 2, 3} do
{1, x, 3} when x > 0 ->
IO.puts("x is positive: #{x}")
{1, x, 3} when x <= 0 ->
IO.puts("x is not positive: #{x}")
_ ->
IO.puts("No match")
end

5. with文でのパターンマッチング

Section titled “5. with文でのパターンマッチング”
def create_user(params) do
with {:ok, user} <- validate_user(params),
{:ok, saved_user} <- save_user(user),
{:ok, _email} <- send_welcome_email(saved_user) do
{:ok, saved_user}
else
{:error, reason} ->
{:error, reason}
end
end

ネストしたパターンマッチング

Section titled “ネストしたパターンマッチング”
def process_order(order_id) do
with {:ok, order} <- fetch_order(order_id),
{:ok, user} <- fetch_user(order.user_id),
{:ok, payment} <- process_payment(order, user) do
{:ok, %{order: order, user: user, payment: payment}}
else
{:error, :order_not_found} ->
{:error, "Order not found"}
{:error, :user_not_found} ->
{:error, "User not found"}
{:error, reason} ->
{:error, "Payment failed: #{inspect(reason)}"}
end
end

6. 実務でのベストプラクティス

Section titled “6. 実務でのベストプラクティス”

パターン1: エラーハンドリング

Section titled “パターン1: エラーハンドリング”
defmodule UserService do
def create_user(params) do
with {:ok, validated} <- validate_params(params),
{:ok, user} <- insert_user(validated),
{:ok, _} <- send_welcome_email(user) do
{:ok, user}
else
{:error, %Ecto.Changeset{} = changeset} ->
{:error, format_changeset_errors(changeset)}
{:error, reason} ->
{:error, reason}
end
end
defp validate_params(params) do
# バリデーションロジック
if valid?(params) do
{:ok, params}
else
{:error, :invalid_params}
end
end
end
defmodule DataTransformer do
def transform(%{status: :ok, data: data}) do
{:ok, process_data(data)}
end
def transform(%{status: :error, reason: reason}) do
{:error, reason}
end
def transform(data) when is_list(data) do
Enum.map(data, &transform/1)
end
def transform(_) do
{:error, :invalid_format}
end
end

問題1: パターンがマッチしない

Section titled “問題1: パターンがマッチしない”

原因:

  • データ構造が期待と異なる
  • ガード節の条件が厳しすぎる

解決策:

# デバッグ用のログを追加
def process(data) do
IO.inspect(data, label: "Received data")
case data do
{:ok, value} -> handle_success(value)
{:error, reason} -> handle_error(reason)
other -> IO.inspect(other, label: "Unexpected pattern")
end
end

原因:

  • 同じ変数名を複数回使用している

解決策:

# ピン演算子を使用
x = 1
{^x, y} = {1, 2} # xの値を固定
# または、異なる変数名を使用
x = 1
{_x, y} = {2, 3} # xを無視

これで、パターンマッチングの基礎知識と実務での使い方を理解できるようになりました。