一键导入
elixir
Guidelines and best practices when writing Elixir code. Claude should use this skill whenever asked to modify, evaluate or review Elixir files (.ex or .exs).
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Guidelines and best practices when writing Elixir code. Claude should use this skill whenever asked to modify, evaluate or review Elixir files (.ex or .exs).
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Audit the last N completed backlog tickets that the autonomous `pi` loop finished, judging the work on Correctness, Conciseness, Clarity, Organization, and Resilience. File any required fixes as tightly-scoped backlog tickets with thorough plans and proper dependencies so the next `pi` round course-corrects. Invoke when checking in on pi's progress, e.g. "review pi's last few tickets".
Autonomous execution skill for tickets (backlog). Use when implementing a ticket (TASK-xxx).
Use when querying, managing, or automating Actual Budget via the command line — accounts, transactions, budgets, categories, payees, schedules, rules, or AQL queries against an Actual Budget server.
Use acli CLI for Atlassian operations — primarily Jira work item management, project operations, sprint tracking, and board management with JQL search. Also covers Confluence page/space/blog operations and a CQL search fallback (acli has no native Confluence search).
Autonomous planning skill for tickets (backlog). Use when planning implementation for a ticket (TASK-xxx). Spawns research subagents, analyzes dependencies, creates sub-tickets for discrete work, and writes detailed implementation plans.
Write or rewrite content in my authentic voice using my voice DNA profile. Use when drafting technical docs, blog posts, RFCs, proposals, or reviewing/editing prose for voice consistency.
| name | elixir |
| description | Guidelines and best practices when writing Elixir code. Claude should use this skill whenever asked to modify, evaluate or review Elixir files (.ex or .exs). |
Expert guidance for writing idiomatic, type-safe Elixir code.
This skill covers multiple areas of Elixir expertise. Reference the appropriate guide based on the task:
See below for:
Use this when writing or reviewing general Elixir code, handling errors, or working with collections.
See typespecs.md for:
any())Use this when adding type annotations, defining behaviours, preparing code for Dialyzer analysis, or documenting function contracts.
See dataloader.md for:
Use this when building GraphQL APIs with Absinthe, optimizing resolver performance, implementing authorization rules, or working with nested GraphQL queries.
Common patterns to apply immediately:
Data Access:
# Use bracket syntax for flexibility
opts[:key] # instead of Map.get(opts, :key)
Error Handling:
# Use with for sequential operations
with {:ok, data} <- fetch(),
{:ok, result} <- process(data) do
result
end
# Use case for critical error handling
case call_service(id) do
{:ok, result} -> result
{:error, error} -> handle_error(error)
end
Type Specifications:
# Be specific, avoid any()
@spec process(String.t(), keyword()) :: {:ok, result()} | {:error, atom()}
# Document custom types
@typedoc "User configuration options"
@type options :: [timeout: pos_integer(), retry: boolean()]
Core patterns for idiomatic Elixir code.
Use bracket access syntax instead of Map.get/2 or Keyword.get/2:
# Avoid - locks you into specific data structure
opts = %{foo: :bar}
Map.get(opts, :foo)
# Prefer - works with maps, keywords, and Access behaviour
opts[:foo]
Side-effecting functions return {:ok, term()} | {:error, term()}. Use with or case to handle results explicitly:
# Avoid - spreads error handling across functions
def main do
data
|> call_service()
|> parse_response()
|> handle_result()
end
defp parse_response({:ok, result}), do: Jason.decode(result)
defp parse_response(error), do: error
# Prefer - caller controls error handling
def main do
with {:ok, response} <- call_service(data),
{:ok, decoded} <- Jason.decode(response) do
decoded
end
end
Rationale: Each function shouldn't need to know how it's called or what order it's composed in. The caller has enough context to decide error handling strategy.
When errors are vital to control flow (circuit breakers, fallbacks, caching), use explicit case statements:
def main(id) do
case :fuse.check(:service) do
:ok ->
case call_service(id) do
{:ok, result} ->
:ok = Cache.put(id, result)
{:ok, result}
{:error, error} ->
:fuse.melt(:service)
{:error, error}
end
:blown ->
case Cache.get(id) do
nil -> {:error, :service_unavailable}
cached -> {:ok, cached}
end
end
end
This increases function size but makes every control path explicit and readable.
Assign intermediate values to variables instead:
# Avoid
build_post(attrs)
|> store_post()
|> case do
{:ok, post} -> # ...
{:error, _} -> # ...
end
# Prefer
changeset = build_post(attrs)
case store_post(changeset) do
{:ok, post} -> # ...
{:error, _} -> # ...
end
Use else only for truly generic error handling. If you need to handle specific errors differently, use case instead:
# Avoid - tagging just to differentiate errors
with {:service, {:ok, resp}} <- {:service, call_service(data)},
{:decode, {:ok, decoded}} <- {:decode, Jason.decode(resp)} do
:ok
else
{:service, {:error, error}} -> # ...
{:decode, {:error, error}} -> # ...
end
# Prefer - use case when errors matter
case call_service(data) do
{:ok, resp} ->
case Jason.decode(resp) do
{:ok, decoded} -> decoded
{:error, error} -> # handle decode error
end
{:error, error} -> # handle service error
end
Alternative: Create a unified error type for consistent error handling across your app:
defmodule MyApp.Error do
defexception [:code, :msg, :meta]
def not_found(msg, meta \\ %{}), do: %__MODULE__{code: :not_found, msg: msg, meta: meta}
def internal(msg, meta \\ %{}), do: %__MODULE__{code: :internal, msg: msg, meta: meta}
end
# Wrap external errors in your unified type
defp decode(resp) do
case Jason.decode(resp) do
{:ok, decoded} -> {:ok, decoded}
{:error, _} -> {:error, Error.internal("could not decode: #{inspect(resp)}")}
end
end
Expose single-item operations and use Enum/Stream at call sites:
# Avoid - hides higher-order functions
def main do
collection
|> parse_items()
|> add_items()
end
def parse_items(list), do: Enum.map(list, &String.to_integer/1)
def add_items(list), do: Enum.reduce(list, 0, & &1 + &2)
# Prefer - makes functions reusable
def main do
collection
|> Enum.map(&parse_item/1)
|> Enum.sum()
end
defp parse_item(item), do: String.to_integer(item)
Benefits: Functions become reusable with Enum, Stream, Task. Better solutions often emerge (like using Enum.sum/1 instead of manual reduce).
# Avoid
def call_service(%{req: req}) when not is_nil(req) do
# ...
end
# Prefer - be explicit about requirements
def call_service(%{req: req}) when is_binary(req) do
# ...
end
If the caller can't do anything about an error, raise or throw instead:
# Avoid - forces caller to handle unrecoverable errors
def get(table \\ __MODULE__, id) do
try do
:ets.lookup(table, id)
catch
_, _ -> {:error, "Table is not available"}
end
end
# Prefer - let it fail if table doesn't exist
def get(table \\ __MODULE__, id) do
:ets.lookup(table, id)
end
Use bang functions (!) when downstream services should always return expected formats:
# Avoid - unnecessary error handling for impossible cases
def main do
{:ok, resp} = call_service(id)
case Jason.decode(resp) do
{:ok, decoded} -> decoded
{:error, e} -> # What can we even do here?
end
end
# Prefer - let it crash and restart
def main do
{:ok, resp} = call_service(id)
Jason.decode!(resp)
end
Use for comprehensions in assertions to get better failure messages:
# Avoid - generic failure message
assert Enum.all?(posts, fn post -> %Post{} = post end)
# Prefer - shows which specific post failed
for post <- posts, do: assert %Post{} = post