第 8 章: パターンマッチとガード節¶
8.1 はじめに¶
この章では、Elixir の分岐を読みやすく保つために、関数頭部のパターンマッチ、when ガード節、case と cond の使い分けを整理します。あわせて、タグ付きタプル {:ok, value} / {:error, reason} による結果表現も確認します。
8.2 関数頭部でのパターンマッチ¶
Elixir では値ごとの分岐を関数定義に直接書けます。
defmodule FizzBuzz.Factory do
alias FizzBuzz.Model.{Type01, Type02, Type03, Value}
def create(1), do: %Type01{value: %Value{number: 1}}
def create(2), do: %Type01{value: %Value{number: 2}}
def create(3), do: %Type02{value: %Value{number: 3}}
def create(5), do: %Type03{value: %Value{number: 5}}
def create(_), do: :unsupported
end
def create(1)、def create(2)、def create(_) のように書くと、if より宣言的で見通しがよくなります。
8.3 ガード節で入力条件を制約する¶
パターンだけで不足する条件は when を使います。
defmodule FizzBuzz.Validator do
def validate(number) when is_integer(number) and number > 0, do: {:ok, number}
def validate(number) when is_integer(number), do: {:error, :non_positive}
def validate(_), do: {:error, :not_integer}
end
when is_integer(number) and number > 0 により、正常系の条件を関数シグネチャ近くで明示できます。
8.4 case 式と cond 式の使い分け¶
case は「値の形」を分岐するときに向いており、cond は「複数の真偽条件」を評価するときに向いています。
defmodule FizzBuzz.Renderer do
def from_result(result) do
case result do
{:ok, value} -> "ok: #{value}"
{:error, reason} -> "error: #{reason}"
end
end
def classify(number) do
cond do
rem(number, 15) == 0 -> :fizz_buzz
rem(number, 3) == 0 -> :fizz
rem(number, 5) == 0 -> :buzz
true -> :number
end
end
end
case:{:ok, value}のようなタグ付きデータを分解する。cond:rem/2の結果など条件式を上から順に判定する。
8.5 ExUnit で分岐をテストする¶
分岐ロジックは仕様が崩れやすいため、タグ付きタプルまで含めてテストします。
defmodule FizzBuzz.PatternMatchingTest do
use ExUnit.Case, async: true
alias FizzBuzz.Factory
alias FizzBuzz.Renderer
alias FizzBuzz.Validator
alias FizzBuzz.Model.Type02
test "create/1 は 3 を Type02 として生成する" do
assert %Type02{} = Factory.create(3)
end
test "validate/1 は正の整数を {:ok, value} で返す" do
assert Validator.validate(10) == {:ok, 10}
end
test "validate/1 は不正な入力を {:error, reason} で返す" do
assert Validator.validate(0) == {:error, :non_positive}
assert Validator.validate("10") == {:error, :not_integer}
end
test "from_result/1 はタグ付きタプルを文字列化する" do
assert Renderer.from_result({:ok, "Fizz"}) == "ok: Fizz"
assert Renderer.from_result({:error, :not_integer}) == "error: not_integer"
end
test "classify/1 は cond で優先順位どおりに判定する" do
assert Renderer.classify(15) == :fizz_buzz
assert Renderer.classify(6) == :fizz
assert Renderer.classify(10) == :buzz
assert Renderer.classify(7) == :number
end
end
8.6 まとめ¶
この章では、関数頭部のパターンマッチ、when ガード節、case と cond の使い分け、タグ付きタプルによる結果表現を確認しました。これらを組み合わせると、分岐が増えても読みやすさと安全性を維持できます。