원클릭으로
elixir-patterns
Elixir-specific language patterns including pattern matching, immutability, protocols, behaviours, and macros
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Elixir-specific language patterns including pattern matching, immutability, protocols, behaviours, and macros
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Use when researching state-of-the-art algorithms for a problem, finding academic papers, comparing algorithm approaches, needing paper citations, or user invokes /algorithm-research
Use when researching algorithms, data structures, optimization, performance, or need modern alternatives to classic algorithms
Use when creating benchmarks, measuring performance, comparing implementations, or user invokes /benchmark. Provides step-by-step workflow for benchmark creation, data generation, and results interpretation.
Use when analyzing code cognitive load, onboarding difficulty, or user invokes /cognitive-audit for complexity analysis and refactoring recommendations
Use when analyzing cognitive load, code complexity, onboarding difficulty, readability concerns, maintainability issues, or applying Ousterhout principles for deep modules, reducing complexity, and strategic programming approaches
Use when implementing concurrent operations, choosing between concurrency models, designing async workflows, or working with parallel data processing
| name | elixir-patterns |
| description | Elixir-specific language patterns including pattern matching, immutability, protocols, behaviours, and macros |
This skill covers patterns specific to the Elixir language. For universal concepts, see the cross-references below.
For cross-cutting concerns implemented in Elixir, see these universal skills:
error-handling skill - Railway-oriented programming, tagged tuplesconcurrency skill and the elixir-patterns skill's otp referencedomain-driven-design skill and the elixir-patterns skill's contexts referencedata-validation skillPattern matching is Elixir's fundamental control flow mechanism.
# Match on data structure shape
def process_result({:ok, value}), do: {:success, value}
def process_result({:error, reason}), do: {:failure, reason}
# Match and destructure structs
def greet(%User{name: name, admin: true}), do: "Hello Admin #{name}"
def greet(%User{name: name}), do: "Hello #{name}"
# Match with guards
def calculate(x) when is_integer(x) and x > 0, do: x * 2
def calculate(x) when is_integer(x), do: 0
def calculate(_), do: {:error, :invalid_input}
When to use:
Prefer pattern matching over conditionals:
# Prefer: match in function head
def process({:ok, value}), do: handle_success(value)
def process({:error, reason}), do: handle_error(reason)
# Avoid: case inside function
def process(result) do
case result do
{:ok, value} -> handle_success(value)
{:error, reason} -> handle_error(reason)
end
end
The pin operator (^) matches against existing variable values:
expected_id = 123
case user do
%User{id: ^expected_id} -> :found
%User{} -> :different_user
end
# Common in with expressions
with {:ok, ^expected} <- validate(input) do
:validated
end
# case: match on structure
case HTTP.get(url) do
{:ok, %{status: 200, body: body}} -> {:ok, body}
{:ok, %{status: 404}} -> {:error, :not_found}
{:ok, %{status: status}} -> {:error, {:http_error, status}}
{:error, reason} -> {:error, reason}
end
# cond: match on boolean conditions
cond do
age < 13 -> :child
age < 20 -> :teenager
age < 65 -> :adult
true -> :senior
end
# Maps
%{name: name, email: email} = user
# Nested
%{user: %{profile: %{bio: bio}}} = data
# Lists
[head | tail] = list
[first, second | rest] = list
# Tuples
{:ok, result} = operation()
# Function arguments
def process(%{items: items, total: total}) do
# items and total bound
end
All data in Elixir is immutable. Transformations create new data structures.
Chain transformations with the pipe operator:
user
|> Map.put(:name, "New Name")
|> Map.put(:updated_at, DateTime.utc_now())
|> Map.update!(:login_count, &(&1 + 1))
Guidelines:
Transform collections:
users
|> Enum.filter(&(&1.active))
|> Enum.map(&format_user/1)
|> Enum.sort_by(& &1.name)
|> Enum.take(10)
# Reduce for aggregation
Enum.reduce(items, 0, fn item, acc -> acc + item.price end)
# Group by attribute
Enum.group_by(users, & &1.role)
# Put/update
Map.put(map, :key, value)
Map.update(map, :key, default, &update_fn/1)
Map.update!(map, :key, &update_fn/1) # Raises if key missing
# Get with default
Map.get(map, :key, default)
# Merge
Map.merge(map1, map2)
Map.merge(map1, map2, fn _k, v1, v2 -> v1 + v2 end)
# Struct update syntax
%{user | name: "Updated", email: "new@example.com"}
Access behaviour for deep updates:
# get_in
get_in(data, [:user, :profile, :bio])
# put_in
put_in(data, [:user, :profile, :bio], "New bio")
# update_in
update_in(data, [:user, :stats, :views], &(&1 + 1))
# With structs (use Access.key)
update_in(user, [Access.key(:profile), Access.key(:bio)], &String.upcase/1)
# Dynamic path
path = [:user, :settings, setting_key]
get_in(data, path)
# List prepend is O(1)
[new_item | existing_list]
# List append is O(n) - avoid
existing_list ++ [new_item]
# Build in reverse, then reverse once
items
|> Enum.reduce([], fn item, acc -> [transform(item) | acc] end)
|> Enum.reverse()
# Or use Enum.map (handles this internally)
Enum.map(items, &transform/1)
Protocols enable polymorphic behavior across types.
defprotocol Serializable do
@doc "Serialize data to JSON-compatible format"
@spec serialize(t) :: map()
def serialize(data)
end
defimpl Serializable, for: User do
def serialize(%User{name: name, email: email, id: id}) do
%{id: id, name: name, email: email}
end
end
defimpl Serializable, for: Product do
def serialize(%Product{id: id, name: name, price: price}) do
%{id: id, name: name, price: Decimal.to_float(price)}
end
end
# For any struct (fallback)
defimpl Serializable, for: Any do
def serialize(struct) do
struct
|> Map.from_struct()
|> Map.drop([:__meta__])
end
end
defmodule User do
@derive {Serializable, only: [:id, :name, :email]}
defstruct [:id, :name, :email, :password_hash]
end
| Protocol | Purpose | Common Implementations |
|---|---|---|
Enumerable | Enable Enum functions | List, Map, Range |
Collectable | Enable into: option | List, Map, MapSet |
Inspect | Custom inspect output | All types |
String.Chars | to_string/1 | Atom, Integer, etc. |
| Aspect | Protocol | Behaviour |
|---|---|---|
| Dispatch | On data type | On module |
| Use case | Polymorphism over data | Contract for modules |
| Definition | defprotocol | @callback |
| Implementation | defimpl | @behaviour + @impl |
Behaviours define module contracts with callback specifications.
defmodule MyApp.PaymentGateway do
@doc "Process a payment charge"
@callback charge(amount :: Money.t(), card :: Card.t()) ::
{:ok, Transaction.t()} | {:error, term()}
@doc "Refund a previous charge"
@callback refund(transaction_id :: String.t(), amount :: Money.t()) ::
{:ok, Refund.t()} | {:error, term()}
@doc "Check gateway health"
@callback health_check() :: :ok | {:error, term()}
# Optional callback with default
@optional_callbacks [health_check: 0]
end
defmodule MyApp.PaymentGateway.Stripe do
@behaviour MyApp.PaymentGateway
@impl true
def charge(amount, card) do
# Stripe-specific implementation
{:ok, %Transaction{}}
end
@impl true
def refund(transaction_id, amount) do
# Stripe-specific implementation
{:ok, %Refund{}}
end
# health_check/0 not implemented (optional)
end
Always use @impl true for callback implementations:
# Good: explicit implementation
@impl true
def handle_call(:get, _from, state) do
{:reply, state, state}
end
# Compiler warns if @impl true on non-callback function
@impl true # Warning: got @impl true for my_helper/1 but...
def my_helper(arg) do
# This is not a behaviour callback, so @impl causes a warning
{:processed, arg}
end
defmodule MyApp.Payments do
def process(gateway_module, amount, card) do
gateway_module.charge(amount, card)
end
end
# Usage
MyApp.Payments.process(MyApp.PaymentGateway.Stripe, amount, card)
# Or from config
gateway = Application.get_env(:my_app, :payment_gateway)
MyApp.Payments.process(gateway, amount, card)
Macros transform code at compile time. Use sparingly.
Good use cases:
Avoid macros when:
defmodule MyApp.Macros do
defmacro debug(expression) do
quote do
value = unquote(expression)
IO.puts("#{unquote(Macro.to_string(expression))} = #{inspect(value)}")
value
end
end
end
# Usage
require MyApp.Macros
MyApp.Macros.debug(1 + 2)
# Prints: 1 + 2 = 3
# Returns: 3
# quote: capture AST
quote do
1 + 2
end
# => {:+, [], [1, 2]}
# unquote: inject value into AST
x = 5
quote do
1 + unquote(x)
end
# => {:+, [], [1, 5]}
# unquote_splicing: inject list as arguments
args = [1, 2, 3]
quote do
sum(unquote_splicing(args))
end
# => {:sum, [], [1, 2, 3]}
Macros are hygienic by default (variables don't leak):
defmacro double(x) do
quote do
result = unquote(x) * 2 # 'result' won't conflict with caller
result
end
end
# Explicitly unhygienic (use with caution)
defmacro assign_x(value) do
quote do
var!(x) = unquote(value) # Assigns to caller's 'x'
end
end
Create short names for modules:
# Single alias
alias MyApp.Accounts.User
# Multiple from same namespace
alias MyApp.Accounts.{User, Token, Session}
# With custom name
alias MyApp.Accounts.User, as: AccountUser
Bring functions into current scope:
# Import all
import Ecto.Query
# Import specific functions
import Ecto.Query, only: [from: 2, where: 3]
# Import except
import Ecto.Query, except: [preload: 3]
# Import macros only
import Ecto.Query, only: :macros
Load module and make macros available:
require Logger
Logger.debug("message")
# Or with alias
require Logger, as: Log
Log.info("message")
Invoke using/1 macro:
# In your module
use GenServer
# Equivalent to
require GenServer
GenServer.__using__([])
# With options
use Phoenix.Controller, namespace: MyAppWeb
defmodule MyApp.Accounts.UserController do
# 1. use directives
use MyAppWeb, :controller
# 2. alias directives (sorted alphabetically)
alias MyApp.Accounts
alias MyApp.Accounts.User
# 3. import directives
import MyApp.Helpers
# 4. require directives
require Logger
# Then the rest of the module
end
elixir-patterns skill's otp referenceelixir-patterns skill's contexts referenceelixir-patterns skill's algorithms reference