본문으로 건너뛰기
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(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

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 / 1,000자

이 양식은 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의 스킬 모두 보기

이 스킬의 문제 신고