{"slug":"elixir-idioms","title":"elixir-idioms","summary":"OTP/BEAM patterns and Elixir idioms — GenServer, Supervisor, Task, Registry, pattern matching, with chains, pipes. Use when designing processes or debugging BEAM issues.","platform":"Claude","tags":[],"authorName":"LLM Mart","authorSlug":"llm-mart","score":0,"source":"github","price":null,"verified":false,"createdAt":"2026-10-04T15:14:12.613028Z","repo":{"url":"https://github.com/oliver-kriska/claude-elixir-phoenix","stars":560,"forks":44,"license":"MIT","updatedAt":"2026-10-02T04:11:38Z"},"bodyHtml":"<hr>\n<h2>name: elixir-idioms\ndescription: \"OTP/BEAM patterns and Elixir idioms — GenServer, Supervisor, Task, Registry, pattern matching, with chains, pipes. Use when designing processes or debugging BEAM issues.\"\neffort: medium\nuser-invocable: false</h2>\n<h1>Elixir Idioms</h1>\n<p>Reference for writing idiomatic Elixir code with BEAM-aware patterns.</p>\n<h2>Iron Laws — Never Violate These</h2>\n<ol>\n<li><strong>NO PROCESS WITHOUT A RUNTIME REASON</strong> — Processes model concurrency, state, isolation—NOT code structure</li>\n<li><strong>MESSAGES ARE COPIED</strong> — Keep messages small (except binaries &gt;64 bytes)</li>\n<li><strong>GUARDS USE <code>and</code>/<code>or</code>/<code>not</code></strong> — Never use short-circuit operators in guards (guards require boolean operands)</li>\n<li><strong>CHANGESETS FOR EXTERNAL DATA</strong> — Use <code>cast/4</code> for user input, <code>change/2</code> for internal</li>\n<li><strong>RESCUE ONLY FOR EXTERNAL CODE</strong> — Never use rescue for control flow</li>\n<li><strong>NO DYNAMIC ATOM CREATION</strong> — <code>String.to_atom(user_input)</code> causes memory leak (atoms aren't GC'd)</li>\n<li><strong>@external_resource FOR COMPILE-TIME FILES</strong> — Modules reading files at compile time MUST declare <code>@external_resource</code></li>\n<li><strong>SUPERVISE ALL LONG-LIVED PROCESSES</strong> — Never bare <code>GenServer.start_link</code>/<code>Agent.start_link</code> in production. Use supervision trees</li>\n<li><strong>WRAP THIRD-PARTY LIBRARY APIs</strong> — Always facade external deps behind a project-owned module. Enables swapping without touching callers</li>\n<li><strong>MIX TASKS START ONLY WHAT THEY NEED</strong> — <code>Mix.Task.run(\"app.config\")</code> + <code>Application.ensure_all_started/1</code>, never <code>Mix.Task.run(\"app.start\")</code> (boots the FULL tree: endpoint port, Oban consuming)</li>\n<li><strong>CAPTURE LOCALE BEFORE SPAWNING</strong> — Gettext/CLDR locale is process-local. Read it in the caller and pass explicitly; a spawned Task/GenServer starts with the default locale</li>\n</ol>\n<h2>BEAM Architecture (Why Elixir Works This Way)</h2>\n<ul>\n<li><strong>Processes are cheap (2.6KB)</strong> — Spawn liberally for concurrency/isolation</li>\n<li><strong>Complete memory isolation</strong> — No shared state, no locks needed</li>\n<li><strong>Messages are copied</strong> (except binaries &gt;64 bytes) — Keep messages small</li>\n<li><strong>Per-process GC</strong> — No global GC pauses</li>\n<li><strong>\"Let it crash\"</strong> — Supervisors restart to known-good state</li>\n</ul>\n<h2>Core Principles</h2>\n<ol>\n<li><strong>Pattern match over conditionals</strong> — Function heads first, then <code>case</code>, then <code>cond</code></li>\n<li><strong>Tagged tuples for expected failures</strong> — <code>{:ok, _}</code>/<code>{:error, _}</code> for expected errors, raise for bugs</li>\n<li><strong>Pipe operator for data transformation</strong> — Start with data, never pipe single calls</li>\n<li><strong>Let it crash</strong> — Handle expected errors, crash on unexpected ones</li>\n<li><strong>Explicit over implicit</strong> — Be clear about intentions</li>\n</ol>\n<h2>Quick Decision Trees</h2>\n<h3>Control Flow</h3>\n<pre><code>Need patterns? → case (or function heads)\nMultiple operations? → with\nBoolean conditions? → cond (multiple) or if (single)\n</code></pre>\n<h3>Error Handling</h3>\n<pre><code>Expected failure? → {:ok, _}/{:error, _} tuples\nUnexpected/bug? → raise exception (let supervisor handle)\nExternal library? → rescue (only here!)\n</code></pre>\n<h3>OTP</h3>\n<pre><code>Need state?\n├─ No → Plain functions\n├─ Simple get/update → Agent or ETS\n├─ Complex messages/timeouts → GenServer\n└─ One-off async → Task\n</code></pre>\n<h2>Quick Patterns</h2>\n<pre><code># Pattern match in function head\ndef process(%{status: :active} = user), do: activate(user)\ndef process(%{status: :inactive} = user), do: deactivate(user)\n\n# with for happy path\nwith {:ok, user} &lt;- get_user(id),\n     {:ok, order} &lt;- create_order(user) do\n  {:ok, order}\nend\n\n# Task for async\nTask.Supervisor.async_nolink(TaskSup, fn -&gt; work() end)\n|&gt; Task.yield(5000) || Task.shutdown(task)\n</code></pre>\n<h2>Common Pitfalls</h2>\n<table>\n<thead>\n<tr>\n<th>Wrong</th>\n<th>Right</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>length(list) == 0</code></td>\n<td><code>list == []</code> or <code>Enum.empty?(list)</code></td>\n</tr>\n<tr>\n<td><code>list ++ [item]</code></td>\n<td><code>[item \\| list] \\|&gt; Enum.reverse()</code></td>\n</tr>\n<tr>\n<td><code>String.to_atom(input)</code></td>\n<td><code>String.to_existing_atom(input)</code></td>\n</tr>\n<tr>\n<td><code>spawn(fn -&gt; log(conn) end)</code></td>\n<td><code>ip = conn.ip; spawn(fn -&gt; log(ip) end)</code></td>\n</tr>\n<tr>\n<td><code>unless condition</code></td>\n<td><code>if !condition</code> (unless deprecated in 1.18)</td>\n</tr>\n</tbody>\n</table>\n<h2>References</h2>\n<p>For detailed patterns, see:</p>\n<ul>\n<li><code>${CLAUDE_SKILL_DIR}/references/pattern-matching.md</code> - Pattern matching, guards, binary matching</li>\n<li><code>${CLAUDE_SKILL_DIR}/references/otp-patterns.md</code> - GenServer, Supervisor, Task, Registry</li>\n<li><code>${CLAUDE_SKILL_DIR}/references/error-handling.md</code> - Tagged tuples, rescue, with</li>\n<li><code>${CLAUDE_SKILL_DIR}/references/with-and-pipes.md</code> - When to use <code>with</code> and <code>|&gt;</code> (idiomatic patterns)</li>\n<li><code>${CLAUDE_SKILL_DIR}/references/troubleshooting.md</code> - Production BEAM debugging (memory, performance, crashes)</li>\n<li><code>${CLAUDE_SKILL_DIR}/references/anti-patterns.md</code> - Common mistakes and fixes</li>\n<li><code>${CLAUDE_SKILL_DIR}/references/mix-tasks.md</code> - Mix task naming, option parsing, shell output</li>\n<li><code>${CLAUDE_SKILL_DIR}/references/elixir-118-features.md</code> - Duration module, dbg improvements (1.18+)</li>\n<li><code>${CLAUDE_SKILL_DIR}/references/elixir-120-type-system.md</code> - Gradual type checker, <code>dynamic()</code>, verified bugs as compile warnings (1.20+, OTP 27+)</li>\n</ul>\n","files":[{"path":"references/anti-patterns.md","sizeBytes":4664,"isText":true},{"path":"references/elixir-118-features.md","sizeBytes":3713,"isText":true},{"path":"references/elixir-120-type-system.md","sizeBytes":6064,"isText":true},{"path":"references/error-handling.md","sizeBytes":2786,"isText":true},{"path":"references/mix-tasks.md","sizeBytes":2922,"isText":true},{"path":"references/otp-patterns.md","sizeBytes":10813,"isText":true},{"path":"references/pattern-matching.md","sizeBytes":1977,"isText":true},{"path":"references/troubleshooting.md","sizeBytes":4597,"isText":true},{"path":"references/with-and-pipes.md","sizeBytes":7384,"isText":true},{"path":"SKILL.md","sizeBytes":4890,"isText":true}],"reviewScore":null,"reviewSummary":null,"trust":{"provenance":"trusted-source-unreviewed","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow.","bodySource":null},"bodyLocked":false,"purchaseUrl":null,"sourceUrl":null,"report":{"provenance":"trusted-source-unreviewed","screen":{"ran":true,"outcome":"clean","suspicious":0,"notes":0,"hiddenCharacters":false},"virusScan":{"engine":"clamav","status":"clean","scannedAt":"2026-10-04T15:14:46.749394Z","sha256":"BBDC5CB74F2BA66AFED027B56A5B5DAA1CC9D281C9F4A5CD41F42772FD6B7938","sizeBytes":22890},"review":null,"source":{"repositoryUrl":"https://github.com/oliver-kriska/claude-elixir-phoenix","path":"plugins/elixir-phoenix/skills/elixir-idioms","license":"MIT","commit":"9767a82d24ddddad553e85f88efc2869a7fd7d88","subtreeSha":"0AC8F34BE4FDB03E59834143584E66C9ECDF7E089F6C0807063E17B485B516CB","lastSyncedAt":"2026-10-04T15:14:09.139242Z"},"reviewedAt":"2026-10-04T15:15:37.752153Z","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow."},"install":[{"target":"skills-cli","command":"npx skills add https://github.com/oliver-kriska/claude-elixir-phoenix/tree/main/plugins/elixir-phoenix/skills/elixir-idioms"},{"target":"claude-code","command":"claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install oliver-kriska-claude-elixir-phoenix@llmmart"},{"target":"git","command":"git clone https://github.com/oliver-kriska/claude-elixir-phoenix.git"}]}