跳到正文
cccskills
免费在 GitHub 公开

elixir

Write and refactor idiomatic Elixir functions, modules, and data structures. Use for pattern matching, control flow, error handling, protocols, behaviours, and deciding whether a process is needed. Use otp for process design and supervision.

中文简介正在准备中。当前显示原文说明。

查看安装方法

包含的文件(1)

  • SKILL.md8.1 KB

SKILL.md(原文)

安装前可以查看交给 AI 代理的指令内容。

Elixir

Design modules, model data, and handle errors with Elixir's functional idioms.

The Iron Law

NO PROCESS WITHOUT A RUNTIME REASON

Before creating a GenServer, Agent, or any process, answer YES to at least one:

  1. Do I need mutable state persisting across calls?
  2. Do I need concurrent execution?
  3. Do I need fault isolation?

All three are NO? Use plain functions. Modules organize code; processes manage runtime.

The Three Decoupled Dimensions

OOP couples behavior, state, and mutability together. Elixir decouples them:

OOP DimensionElixir Equivalent
BehaviorModules (functions)
StateData (structs, maps)
MutabilityProcesses (GenServer)

Pick only what you need. "I only need data and functions" = no process needed.

"Let It Crash" = "Let It Heal"

The misconception: Write careless code. The truth: Supervisors START processes.

  • Handle expected errors explicitly ({:ok, _} / {:error, _})
  • Let unexpected errors crash → supervisor restarts

Control Flow

Pattern matching first:

  • Match on function heads instead of if/else or case in bodies
  • %{} matches ANY map—use map_size(map) == 0 guard for empty maps
  • Avoid nested case—refactor to single case, with, or separate functions

Error handling:

  • Use {:ok, result} / {:error, reason} for operations that can fail
  • Avoid raising exceptions for control flow
  • Use with for chaining {:ok, _} / {:error, _} operations

Be explicit about expected cases:

  • Avoid _ -> nil catch-alls—they silently swallow unexpected cases
  • Avoid value && value.field nil-punning—obscures actual return types
  • When a case has {:ok, nil} -> nil alongside {:ok, value} -> value.field, use with instead:
# Verbose
case get_run(id) do
  {:ok, nil} -> nil
  {:ok, run} -> run.recommendations
end

# Prefer
with {:ok, %{recommendations: recs}} <- get_run(id), do: recs

Polymorphism

For Polymorphism Over...UseContract
ModulesBehaviorsUpfront callbacks
DataProtocolsUpfront implementations
ProcessesMessage passingImplicit (send/receive)

Behaviors = default for module polymorphism (very cheap at runtime) Protocols = only when composing data types, especially built-ins Message passing = only when stateful by design (IO, file handles)

Use the simplest abstraction: pattern matching → anonymous functions → behaviors → protocols → message passing. Each step adds complexity.

When justified: Library extensibility, multiple implementations, test swapping. When to stay coupled: Internal module, single implementation, pattern matching handles all cases.

Data Modeling Replaces Class Hierarchies

OOP: Complex class hierarchy + visitor pattern. Elixir: Model as data + pattern matching + recursion.

{:sequence, {:literal, "rain"}, {:repeat, {:alternation, "dogs", "cats"}}}

def interpret({:literal, text}, input), do: ...
def interpret({:sequence, left, right}, input), do: ...
def interpret({:repeat, pattern}, input), do: ...

Defaults and Options

Use /3 variants (Keyword.get/3, Map.get/3) instead of case statements branching on nil:

# WRONG
case Keyword.get(opts, :chunker) do
  nil -> chunker()
  config -> parse_chunker_config(config)
end

# RIGHT
Keyword.get(opts, :chunker, :default) |> parse_chunker_config()

Don't create helper functions to merge config defaults. Inline the fallback:

# WRONG
defp merge_defaults(opts), do: Keyword.merge([repo: Application.get_env(:app, :repo)], opts)

# RIGHT
def some_function(opts) do
  repo = opts[:repo] || Application.get_env(:app, :repo)
end

Idioms

  • Process dictionary is typically unidiomatic—pass state explicitly
  • Reserve is_thing names for guards only
  • Use structs over maps when shape is known: defstruct [:name, :age]
  • Prepend to lists [new | list] not list ++ [new]
  • Use dbg/1 for debugging—prints formatted value with context
  • Use built-in JSON module (Elixir 1.18+) instead of Jason

Verification

Inside coding agents, always prefix mix commands with unbuffer to get ANSI colors and prevent stdout block-buffering in non-TTY environments (e.g. unbuffer mix test). Install: brew install expect (macOS) or apt install expect (Linux). If unbuffer is unavailable, report the missing prerequisite instead of silently dropping it.

After changing Elixir code, verify the completed change before reporting it as done. Run commands from the relevant Mix project using its pinned Elixir/OTP versions. Follow the repository's contribution instructions and existing check aliases. Prefer an alias when it covers the checks below, and run any uncovered checks separately:

  1. Format changed files with unbuffer mix format path/to/file.ex path/to/test.exs, following the project's formatter configuration.
  2. Compile with unbuffer mix compile --warnings-as-errors to catch compilation errors and warnings.
  3. Run relevant tests with unbuffer mix test test/path/to/affected_test.exs. Run the broader suite when the change affects shared behavior or the repository requires it.
  4. Run unbuffer mix credo when Credo is configured, using the repository's flags and configuration.

Fix failures introduced by the change and rerun the affected checks. Report the commands actually run and their results, including any checks that were skipped or blocked and why. An unrun or blocked check hasn't passed.

Testing

Prefer pattern matching over imperative assertions. Never use assert length + Enum.at/List.last/hd. Pattern match checks length and content in one shot:

# Bad
assert length(students) == 2
assert Enum.at(students, 0).name == "Alice"
assert Enum.at(students, 1).name == "Bob"

# Good
assert [%{name: "Alice"}, %{name: "Bob"}] = students

Same goes for type-only predicates: assert is_map(user) / assert is_list(posts) pass for almost any non-error return. Pattern match the shape and content together: assert %User{email: "a@b.com"} = user. is_nil/1 is fine when nil-ness is the whole point.

Test behavior, not implementation. Test use cases / public API. Refactoring shouldn't break tests.

Test your code, not the framework. If deleting your code doesn't fail the test, it's tautological.

Keep tests async. async: false means you've coupled to global state. Fix the coupling:

ProblemSolution
Application.put_envPass config as function argument
Feature flagsInject via process dictionary or context
ETS tablesCreate per-test tables with unique names
External APIsUse Mox with explicit allowances
File system operationsUse @tag :tmp_dir (see below)

Use tmp_dir for file tests. ExUnit creates unique temp directories per test, async-safe:

@tag :tmp_dir
test "writes file", %{tmp_dir: tmp_dir} do
  path = Path.join(tmp_dir, "test.txt")
  File.write!(path, "content")
  assert File.read!(path) == "content"
end

Directory is auto-cleaned before each run. Works with @moduletag :tmp_dir for all tests in module.

Common Rationalizations

ExcuseReality
"I need a process to organize this code"Modules organize code. Processes are for runtime.
"GenServer is the Elixir way"Plain functions are also the Elixir way.
"I'll need state eventually"YAGNI. Add process when you need it.
"It's just a simple wrapper process"Simple wrappers become bottlenecks.
"This is how I'd structure it in OOP"Rethink from data flow.

Red Flags - STOP and Reconsider

  • Creating process without answering the three questions
  • Using GenServer for stateless operations
  • Wrapping a library in a process "for safety"
  • One process per entity without runtime justification
  • Reaching for protocols when pattern matching works

Any of these? Re-read The Iron Law.

评价

还没有评价。欢迎分享你的使用感受。

评论

无需登录即可发表。欢迎分享使用感受、提问,或说明在哪些环境下无法运行。

还没有评论。来写第一条吧。

提交后会立即公开。请勿填写邮箱、电话等个人信息。URL 最多 2 个。

0 / 1000 字

此表单受 reCAPTCHA 保护,适用 Google 的隐私权政策和服务条款。

同一仓库的技能

ecto

免费

Design and debug Elixir persistence with Ecto. Use for schemas, changesets, queries, preloads, transactions, migrations, multi-tenancy, and data access through application contexts. Covers database query performance and connection pools.

中文简介正在准备中。当前显示原文说明。

georgeguimaraes/elixir-agent-tools1892026年9月20日 更新

oban

免费

Build and debug durable background jobs with Oban and Oban Pro. Use for workers, job argument serialization, retries, scheduled or recurring jobs, uniqueness, batches, and workflows. Use otp for in-memory tasks and process supervision.

中文简介正在准备中。当前显示原文说明。

georgeguimaraes/elixir-agent-tools1892026年9月20日 更新

otp

免费

Design and debug Elixir runtime concurrency, process state, and supervision. Use for GenServer, Supervisor, Task, Registry, ETS, process bottlenecks, and Broadway pipelines. Use oban for durable background jobs, retries, and scheduling.

中文简介正在准备中。当前显示原文说明。

georgeguimaraes/elixir-agent-tools1892026年9月20日 更新

phoenix

免费

Build and debug Phoenix web interfaces and HTTP endpoints. Use for LiveView lifecycle and data loading, components, forms, routes, controllers, Plug, channels, and PubSub. Use ecto for changesets, queries, and persistence behind those interfaces.

中文简介正在准备中。当前显示原文说明。

georgeguimaraes/elixir-agent-tools1892026年9月20日 更新

查看 georgeguimaraes 的全部技能

报告此技能的问题