programming-elixir · git:20260503.f77bcc0 · 2026-05-03 · sha256 c60169e2f94d904c

programming-elixir git:20260503.f77bcc0A

Immutable. This exact content is served forever at /api/v1/blob/c60169e2f94d904c.

---
name: programming-elixir
title: "Elixir Development"
description: "Elixir/OTP conventions, pattern matching, GenServer, supervision trees, Phoenix, and functional programming best practices. Auto-activates in Elixir projects."
license: Apache-2.0
compatibility: "Requires Elixir and Erlang/OTP."
domains: developer
rules:
  - file(mix.exs)
  - content(elixir)
---

# Elixir Development

## Conventions

- Idiomatic Elixir — pattern matching over conditionals, pipes over nesting
- "Let it crash" — don't defensively catch errors; let supervisors restart
- `{:ok, _}` / `{:error, _}` tuples — NOT exceptions for control flow
- Immutability — all data is immutable; "updating" returns new data
- OTP first — use GenServer/Supervisor/Agent/Task
- `@impl true` on ALL behaviour callbacks
- `defp` for internal functions — only expose what's needed
- `@spec` on all public functions

## Pattern Matching

- Multi-clause functions over `if`/`else`/`case` nesting
- Destructure in function arguments — not inside body
- Guards (`when is_binary/is_integer`) for type constraints

## Pipe Operator

- Data flows left to right: `user |> Map.get(:email) |> String.downcase()`
- First argument is always piped
- Use `then/1` when piped value isn't the first argument

## Error Handling

- `with` blocks for chaining failable operations
- `{:ok, _}` / `{:error, _}` for expected failures
- `raise`/`rescue` ONLY at system boundaries
- Provide context: `{:error, {:not_found, id}}`

## OTP Patterns

- GenServer: always provide client API wrapping `GenServer.call`/`cast`
- Supervisor: `:one_for_one` default, `:rest_for_one` for dependent children
- Agent: for simple state when GenServer is overkill
- Task / Task.Supervisor: for async one-off work
- DynamicSupervisor: for children started at runtime

## Testing

- ExUnit with `async: true` for concurrent tests
- `describe` blocks to group related tests
- `setup` / `setup_all` for fixtures
- `assert` with pattern matching: `assert {:ok, %User{}} = fetch_user(id)`
- Mox for behaviour-based mocking

## Common Pitfalls

- `%{map | key: val}` only updates EXISTING keys — use `Map.put/3` for new
- Atoms from user input are never GC'd — use `String.to_existing_atom/1`
- Enum vs Stream: use Stream for large/lazy collections