elixir-pattern-matching

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Elixir Pattern Matching

Elixir 模式匹配

Master pattern matching in Elixir to write elegant, declarative code. This skill covers function patterns, case statements, guards, and destructuring across various data structures.
掌握Elixir中的模式匹配,编写优雅的声明式代码。本技能涵盖函数模式、case语句、Guards以及针对各种数据结构的解构操作。

Basic Pattern Matching

基础模式匹配

elixir
undefined
elixir
undefined

Simple assignment is pattern matching

Simple assignment is pattern matching

x = 1 1 = x # This works because x matches 1
x = 1 1 = x # This works because x matches 1

Pattern matching with tuples

Pattern matching with tuples

{:ok, value} = {:ok, "success"} value # => "success"
{:ok, value} = {:ok, "success"} value # => "success"

Will raise MatchError if patterns don't match

Will raise MatchError if patterns don't match

{:error, _} = {:ok, "success"} # MatchError

{:error, _} = {:ok, "success"} # MatchError

Pin operator to use existing value

Pin operator to use existing value

x = 1 ^x = 1 # Works
x = 1 ^x = 1 # Works

^x = 2 # MatchError

^x = 2 # MatchError

Ignore values with underscore

Ignore values with underscore

{:ok, } = {:ok, "any value"} {, _, third} = {1, 2, 3} third # => 3
undefined
{:ok, } = {:ok, "any value"} {, _, third} = {1, 2, 3} third # => 3
undefined

Function Pattern Matching

函数模式匹配

elixir
defmodule Calculator do
  def add(a, b), do: a + b

  def factorial(0), do: 1
  def factorial(n) when n > 0, do: n * factorial(n - 1)

  def describe_tuple({:ok, value}) do
    "Success: #{value}"
  end

  def describe_tuple({:error, reason}) do
    "Error: #{reason}"
  end

  def describe_tuple(_) do
    "Unknown tuple format"
  end
end
elixir
defmodule Calculator do
  def add(a, b), do: a + b

  def factorial(0), do: 1
  def factorial(n) when n > 0, do: n * factorial(n - 1)

  def describe_tuple({:ok, value}) do
    "Success: #{value}"
  end

  def describe_tuple({:error, reason}) do
    "Error: #{reason}"
  end

  def describe_tuple(_) do
    "Unknown tuple format"
  end
end

Usage

Usage

Calculator.factorial(5) # => 120 Calculator.describe_tuple({:ok, "done"}) # => "Success: done"
undefined
Calculator.factorial(5) # => 120 Calculator.describe_tuple({:ok, "done"}) # => "Success: done"
undefined

Guards in Pattern Matching

模式匹配中的Guards

elixir
defmodule NumberChecker do
  def check(x) when is_integer(x) and x > 0 do
    "Positive integer"
  end

  def check(x) when is_integer(x) and x < 0 do
    "Negative integer"
  end

  def check(0), do: "Zero"

  def check(x) when is_float(x), do: "Float"

  def check(_), do: "Not a number"
end

defmodule Validator do
  def valid_email?(email) when is_binary(email) do
    String.contains?(email, "@")
  end

  def valid_email?(_), do: false

  def in_range?(num, min, max)
      when is_number(num) and num >= min and num <= max do
    true
  end

  def in_range?(_, _, _), do: false
end
elixir
defmodule NumberChecker do
  def check(x) when is_integer(x) and x > 0 do
    "Positive integer"
  end

  def check(x) when is_integer(x) and x < 0 do
    "Negative integer"
  end

  def check(0), do: "Zero"

  def check(x) when is_float(x), do: "Float"

  def check(_), do: "Not a number"
end

defmodule Validator do
  def valid_email?(email) when is_binary(email) do
    String.contains?(email, "@")
  end

  def valid_email?(_), do: false

  def in_range?(num, min, max)
      when is_number(num) and num >= min and num <= max do
    true
  end

  def in_range?(_, _, _), do: false
end

Case Statements

Case语句

elixir
defmodule ResponseHandler do
  def handle(response) do
    case response do
      {:ok, data} ->
        {:success, data}

      {:error, :not_found} ->
        {:failure, "Resource not found"}

      {:error, :timeout} ->
        {:failure, "Request timed out"}

      {:error, reason} ->
        {:failure, "Error: #{inspect(reason)}"}

      _ ->
        {:failure, "Unknown response"}
    end
  end

  def parse_number(str) do
    case Integer.parse(str) do
      {num, ""} -> {:ok, num}
      {num, _remainder} -> {:ok, num}
      :error -> {:error, "Not a valid number"}
    end
  end
end
elixir
defmodule ResponseHandler do
  def handle(response) do
    case response do
      {:ok, data} ->
        {:success, data}

      {:error, :not_found} ->
        {:failure, "Resource not found"}

      {:error, :timeout} ->
        {:failure, "Request timed out"}

      {:error, reason} ->
        {:failure, "Error: #{inspect(reason)}"}

      _ ->
        {:failure, "Unknown response"}
    end
  end

  def parse_number(str) do
    case Integer.parse(str) do
      {num, ""} -> {:ok, num}
      {num, _remainder} -> {:ok, num}
      :error -> {:error, "Not a valid number"}
    end
  end
end

With Statement for Pipeline Pattern Matching

用于流水线模式匹配的With语句

elixir
defmodule UserService do
  def create_user(params) do
    with {:ok, email} <- validate_email(params["email"]),
         {:ok, password} <- validate_password(params["password"]),
         {:ok, user} <- insert_user(email, password),
         {:ok, _} <- send_welcome_email(user) do
      {:ok, user}
    else
      {:error, reason} -> {:error, reason}
      _ -> {:error, "Unknown error"}
    end
  end

  defp validate_email(email) when is_binary(email) do
    if String.contains?(email, "@") do
      {:ok, email}
    else
      {:error, "Invalid email"}
    end
  end

  defp validate_email(_), do: {:error, "Email required"}

  defp validate_password(pass) when is_binary(pass) do
    if String.length(pass) >= 8 do
      {:ok, pass}
    else
      {:error, "Password too short"}
    end
  end

  defp validate_password(_), do: {:error, "Password required"}

  defp insert_user(email, password) do
    {:ok, %{id: 1, email: email}}
  end

  defp send_welcome_email(_user) do
    {:ok, "sent"}
  end
end
elixir
defmodule UserService do
  def create_user(params) do
    with {:ok, email} <- validate_email(params["email"]),
         {:ok, password} <- validate_password(params["password"]),
         {:ok, user} <- insert_user(email, password),
         {:ok, _} <- send_welcome_email(user) do
      {:ok, user}
    else
      {:error, reason} -> {:error, reason}
      _ -> {:error, "Unknown error"}
    end
  end

  defp validate_email(email) when is_binary(email) do
    if String.contains?(email, "@") do
      {:ok, email}
    else
      {:error, "Invalid email"}
    end
  end

  defp validate_email(_), do: {:error, "Email required"}

  defp validate_password(pass) when is_binary(pass) do
    if String.length(pass) >= 8 do
      {:ok, pass}
    else
      {:error, "Password too short"}
    end
  end

  defp validate_password(_), do: {:error, "Password required"}

  defp insert_user(email, password) do
    {:ok, %{id: 1, email: email}}
  end

  defp send_welcome_email(_user) do
    {:ok, "sent"}
  end
end

List Pattern Matching

列表模式匹配

elixir
defmodule ListOps do
  def sum([]), do: 0
  def sum([head | tail]), do: head + sum(tail)

  def first([head | _tail]), do: head
  def first([]), do: nil

  def second([_, second | _]), do: second
  def second(_), do: nil

  def take_first_three([a, b, c | _rest]) do
    [a, b, c]
  end

  def take_first_three(list), do: list

  def split_at_middle(list) do
    middle = div(length(list), 2)
    {Enum.take(list, middle), Enum.drop(list, middle)}
  end
end
elixir
defmodule ListOps do
  def sum([]), do: 0
  def sum([head | tail]), do: head + sum(tail)

  def first([head | _tail]), do: head
  def first([]), do: nil

  def second([_, second | _]), do: second
  def second(_), do: nil

  def take_first_three([a, b, c | _rest]) do
    [a, b, c]
  end

  def take_first_three(list), do: list

  def split_at_middle(list) do
    middle = div(length(list), 2)
    {Enum.take(list, middle), Enum.drop(list, middle)}
  end
end

Map Pattern Matching

映射(Map)模式匹配

elixir
defmodule UserHandler do
  def greet(%{name: name, age: age}) do
    "Hello #{name}, you are #{age} years old"
  end

  def greet(%{name: name}) do
    "Hello #{name}"
  end

  def admin?(%{role: "admin"}), do: true
  def admin?(_), do: false

  def process_user(%{id: id, name: name} = user) do
    # Can use both the whole user and destructured parts
    IO.puts("Processing user #{id}: #{name}")
    user
  end

  def update_status(%{status: old_status} = user, new_status) do
    %{user | status: new_status}
  end
end

defmodule ConfigParser do
  def get_database_url(config) do
    case config do
      %{database: %{host: host, port: port, name: db}} ->
        "postgresql://#{host}:#{port}/#{db}"

      %{database: %{url: url}} ->
        url

      _ ->
        "postgresql://localhost:5432/default"
    end
  end
end
elixir
defmodule UserHandler do
  def greet(%{name: name, age: age}) do
    "Hello #{name}, you are #{age} years old"
  end

  def greet(%{name: name}) do
    "Hello #{name}"
  end

  def admin?(%{role: "admin"}), do: true
  def admin?(_), do: false

  def process_user(%{id: id, name: name} = user) do
    # Can use both the whole user and destructured parts
    IO.puts("Processing user #{id}: #{name}")
    user
  end

  def update_status(%{status: old_status} = user, new_status) do
    %{user | status: new_status}
  end
end

defmodule ConfigParser do
  def get_database_url(config) do
    case config do
      %{database: %{host: host, port: port, name: db}} ->
        "postgresql://#{host}:#{port}/#{db}"

      %{database: %{url: url}} ->
        url

      _ ->
        "postgresql://localhost:5432/default"
    end
  end
end

Struct Pattern Matching

结构体(Struct)模式匹配

elixir
defmodule User do
  defstruct [:id, :name, :email, role: "user"]
end

defmodule StructMatcher do
  def display_user(%User{name: name, email: email}) do
    "#{name} <#{email}>"
  end

  def is_admin?(%User{role: "admin"}), do: true
  def is_admin?(%User{}), do: false

  def update_email(%User{} = user, new_email) do
    %User{user | email: new_email}
  end
end
elixir
defmodule User do
  defstruct [:id, :name, :email, role: "user"]
end

defmodule StructMatcher do
  def display_user(%User{name: name, email: email}) do
    "#{name} <#{email}>"
  end

  def is_admin?(%User{role: "admin"}), do: true
  def is_admin?(%User{}), do: false

  def update_email(%User{} = user, new_email) do
    %User{user | email: new_email}
  end
end

Usage

Usage

user = %User{id: 1, name: "Alice", email: "alice@example.com"} StructMatcher.display_user(user)
undefined
user = %User{id: 1, name: "Alice", email: "alice@example.com"} StructMatcher.display_user(user)
undefined

Binary Pattern Matching

二进制(Binary)模式匹配

elixir
defmodule BinaryParser do
  def parse_header(<<
        magic::binary-size(4),
        version::16,
        flags::8,
        rest::binary
      >>) do
    %{
      magic: magic,
      version: version,
      flags: flags,
      payload: rest
    }
  end

  def parse_ipv4(<<a, b, c, d>>) do
    "#{a}.#{b}.#{c}.#{d}"
  end

  def parse_utf8(<<codepoint::utf8, rest::binary>>) do
    {codepoint, rest}
  end

  def extract_first_byte(<<first::8, _::binary>>) do
    first
  end
end
elixir
defmodule BinaryParser do
  def parse_header(<<
        magic::binary-size(4),
        version::16,
        flags::8,
        rest::binary
      >>) do
    %{
      magic: magic,
      version: version,
      flags: flags,
      payload: rest
    }
  end

  def parse_ipv4(<<a, b, c, d>>) do
    "#{a}.#{b}.#{c}.#{d}"
  end

  def parse_utf8(<<codepoint::utf8, rest::binary>>) do
    {codepoint, rest}
  end

  def extract_first_byte(<<first::8, _::binary>>) do
    first
  end
end

Cond for Multiple Conditions

多条件判断Cond

elixir
defmodule GradeCalculator do
  def letter_grade(score) do
    cond do
      score >= 90 -> "A"
      score >= 80 -> "B"
      score >= 70 -> "C"
      score >= 60 -> "D"
      true -> "F"
    end
  end

  def describe_number(n) do
    cond do
      n < 0 -> "negative"
      n == 0 -> "zero"
      n > 0 and n < 10 -> "small positive"
      n >= 10 and n < 100 -> "medium positive"
      true -> "large positive"
    end
  end
end
elixir
defmodule GradeCalculator do
  def letter_grade(score) do
    cond do
      score >= 90 -> "A"
      score >= 80 -> "B"
      score >= 70 -> "C"
      score >= 60 -> "D"
      true -> "F"
    end
  end

  def describe_number(n) do
    cond do
      n < 0 -> "negative"
      n == 0 -> "zero"
      n > 0 and n < 10 -> "small positive"
      n >= 10 and n < 100 -> "medium positive"
      true -> "large positive"
    end
  end
end

Advanced Pattern Matching

高级模式匹配

elixir
defmodule AdvancedMatcher do
  # Pattern matching in function arguments with multiple clauses
  def process([]), do: :empty
  def process([_]), do: :single
  def process([_, _]), do: :pair
  def process([h | t]) when length(t) > 1, do: :multiple

  # Pattern matching with maps and guards
  def format_response(%{status: status, body: body})
      when status >= 200 and status < 300 do
    {:ok, body}
  end

  def format_response(%{status: status, body: body})
      when status >= 400 do
    {:error, body}
  end

  # Nested pattern matching
  def extract_user_city(%{
        user: %{address: %{city: city}}
      }) do
    {:ok, city}
  end

  def extract_user_city(_), do: {:error, :no_city}

  # Pattern matching in for comprehensions
  def extract_ok_values(results) do
    for {:ok, value} <- results, do: value
  end
end
elixir
defmodule AdvancedMatcher do
  # Pattern matching in function arguments with multiple clauses
  def process([]), do: :empty
  def process([_]), do: :single
  def process([_, _]), do: :pair
  def process([h | t]) when length(t) > 1, do: :multiple

  # Pattern matching with maps and guards
  def format_response(%{status: status, body: body})
      when status >= 200 and status < 300 do
    {:ok, body}
  end

  def format_response(%{status: status, body: body})
      when status >= 400 do
    {:error, body}
  end

  # Nested pattern matching
  def extract_user_city(%{
        user: %{address: %{city: city}}
      }) do
    {:ok, city}
  end

  def extract_user_city(_), do: {:error, :no_city}

  # Pattern matching in for comprehensions
  def extract_ok_values(results) do
    for {:ok, value} <- results, do: value
  end
end

When to Use This Skill

何时使用本技能

Use elixir-pattern-matching when you need to:
  • Write expressive, declarative control flow
  • Handle different data shapes with function clauses
  • Extract values from complex data structures
  • Validate data formats at function boundaries
  • Implement clean error handling with tagged tuples
  • Parse binary data or protocols
  • Build robust, maintainable Elixir applications
  • Leverage Elixir's functional programming strengths
  • Create clear, self-documenting code
在以下场景中使用Elixir模式匹配:
  • 编写富有表达力的声明式控制流
  • 通过函数子句处理不同的数据结构
  • 从复杂数据结构中提取值
  • 在函数边界验证数据格式
  • 使用标记元组实现清晰的错误处理
  • 解析二进制数据或协议
  • 构建健壮、可维护的Elixir应用
  • 发挥Elixir函数式编程的优势
  • 编写清晰、自文档化的代码

Best Practices

最佳实践

  • Use pattern matching instead of if/else when possible
  • Order function clauses from most specific to most general
  • Use guards to add constraints to patterns
  • Leverage the pin operator when you need existing values
  • Use underscore for values you don't care about
  • Prefer pattern matching over accessor functions
  • Use with statements for complex validation pipelines
  • Keep patterns readable and not overly complex
  • Document complex pattern matching logic
  • Use tagged tuples {:ok, val} and {:error, reason} consistently
  • 尽可能使用模式匹配替代if/else
  • 函数子句按从最具体到最通用的顺序排列
  • 使用Guards为模式添加约束
  • 当需要使用现有值时,使用固定操作符(pin operator)
  • 用下划线标记不需要关注的值
  • 优先使用模式匹配而非访问器函数
  • 对于复杂的验证流水线,使用with语句
  • 保持模式可读性,避免过于复杂
  • 为复杂的模式匹配逻辑添加文档
  • 始终如一地使用标记元组{:ok, val}和{:error, reason}

Common Pitfalls

常见陷阱

  • Forgetting that = is pattern matching, not assignment
  • Not ordering function clauses correctly (specific to general)
  • Overusing guards when simpler patterns would work
  • Not handling all possible pattern cases
  • Creating MatchErrors by not handling edge cases
  • Forgetting to use pin operator when needed
  • Making patterns too complex and hard to read
  • Not using with statement for multi-step validations
  • Ignoring compiler warnings about unused variables
  • Not leveraging pattern matching for cleaner code
  • 忘记=是模式匹配而非赋值操作
  • 函数子句顺序错误(应从具体到通用)
  • 在简单模式可满足需求时过度使用Guards
  • 未处理所有可能的模式情况
  • 因未处理边缘情况导致MatchError
  • 需要时忘记使用固定操作符
  • 创建过于复杂、难以阅读的模式
  • 未使用with语句处理多步骤验证
  • 忽略编译器关于未使用变量的警告
  • 未利用模式匹配编写更简洁的代码

Resources

参考资源