Use when writing Elixir code. Contains paradigm-shifting insights about processes, polymorphism, and the BEAM runtime that differ from OOP thinking.
/plugin marketplace add georgeguimaraes/claude-code-elixir/plugin install elixir-thinking@claude-code-elixirThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Mental shifts required before writing Elixir. These insights contradict conventional OOP patterns.
NO PROCESS WITHOUT A RUNTIME REASON
Before creating a GenServer, Agent, or any process, answer YES to at least one:
All three are NO? Use plain functions. Delete the process. Start over.
No exceptions:
In OOP, objects couple three things together:
Elixir decouples these into independent building blocks:
| OOP Dimension | Elixir Equivalent |
|---|---|
| Behavior | Modules (functions) |
| State | Data (structs, maps) |
| Mutability | Processes (GenServer) |
Why this matters: Pick only what you need. "I only need data and functions" = no process needed.
"Use processes only to model runtime properties—mutable state, concurrency, failures—never for code organization."
Before creating a process, ask:
If none apply, use plain functions. Modules organize code; processes manage runtime.
The misconception: Write careless code.
The truth: Supervisors are designed to START processes.
"You don't need unbreakable software. You need repairable software."
{:ok, _} / {:error, _})Use the simplest abstraction that solves the problem:
Each step adds complexity. Don't reach for behaviors when pattern matching works.
| For Polymorphism Over... | Use | Contract |
|---|---|---|
| Modules | Behaviors | Upfront callbacks |
| Data | Protocols | Upfront implementations |
| Processes | Message passing | Implicit (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)
"BEAM is an operating system for your code."
Processes are not lightweight threads—they're independent programs:
This enables:
Process.exit(pid, :kill) with strong guaranteesOOP problem: Every 'A' character as new object = memory explosion → Flyweight pattern (object pools).
Elixir non-problem: Immutable data = compiler automatically shares identical literals.
def char_a do
%Character{value: "A"}
end
# Automatically optimized: all calls share one memory location
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: ...
Tuples, atoms, and pattern matching replace entire class hierarchies.
"We don't decouple because we have to. We need a reason—decoupling introduces complexity."
When justified:
When to stay coupled:
Stop using vague terms like "readable." Use "clarity":
Clarity test: As a reasonably fluent reader, can I effortlessly understand:
Separation of concerns test: Can each part be understood in isolation? If not, don't split.
"The unit we're testing is the unit of behavior, not the unit of code."
async: true is the default for a reason—parallel tests are fast and expose hidden dependencies.
Tests that mutate global state force async: false:
# WRONG: Forces sequential tests
Application.put_env(:my_app, :feature_flag, true)
Global state that breaks async tests:
Application.put_env/3 — Shared across all testsSolutions that preserve async: true:
| Problem | Solution |
|---|---|
| Config values | Pass config as function argument |
| Feature flags | Inject via process dictionary or context |
| ETS tables | Create per-test tables with unique names |
| External APIs | Use Mox with explicit allowances |
# GOOD: Dependency injection
def send_email(user, mailer \\ MyApp.Mailer) do
mailer.deliver(user.email, "Welcome!")
end
# GOOD: Config as argument
def connect(opts \\ []) do
timeout = Keyword.get(opts, :timeout, Application.get_env(:my_app, :timeout))
# ...
end
The rule: If you need async: false, you've coupled to global state. Fix the coupling, not the test.
Design order:
Each layer independently testable and composable.
Many design patterns are built into the language.
| Excuse | Reality |
|---|---|
| "I need a process to organize this code" | Modules organize code. Processes are for runtime. |
| "GenServer is the Elixir way" | GenServer is ONE tool. Plain functions are also the Elixir way. |
| "I'll need state eventually" | YAGNI. Add process when you need it, not before. |
| "It's just a simple wrapper process" | Simple wrappers become bottlenecks. Use functions. |
| "This is how I'd structure it in OOP" | OOP patterns don't translate. Rethink from data flow. |
| "I need a singleton" | You probably need a module with functions. |
| "Behaviors require a process" | Behaviors define callbacks. Many don't need processes. |
| "I want to encapsulate this" | Modules encapsulate. Processes add runtime overhead. |
| "It feels more structured" | Structure comes from data design, not processes. |
| "Let it crash means I need processes" | Let it crash means supervisors restart. Functions can crash too. |
Any of these? Re-read The Iron Law.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.