Intent in-elixir-essentials
Elixir coding rules: pattern matching, tagged tuples, pipes, naming conventions, callback annotations
install
source · Clone the upstream repo
git clone https://github.com/matthewsinclair/intent
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/matthewsinclair/intent "$T" && mkdir -p ~/.claude/skills && cp -r "$T/intent/plugins/claude/skills/in-elixir-essentials" ~/.claude/skills/matthewsinclair-intent-in-elixir-essentials && rm -rf "$T"
manifest:
intent/plugins/claude/skills/in-elixir-essentials/SKILL.mdsource content
Elixir Essentials
Core Elixir coding rules enforced on every line of generated code. These are mandatory -- no exceptions.
Rules
1. Multi-clause pattern matching over conditionals
NEVER use nested
if/case/cond to branch on struct or map fields. Use multiple function heads with destructuring. Each clause should be a single expression.
# BAD def process(user) do if user.status == :active do if user.role == :admin do :allowed else :denied end else :inactive end end # GOOD def process(%{status: :active, role: :admin}), do: :allowed def process(%{status: :active}), do: :denied def process(_), do: :inactive
Use guards for type-based or range-based decisions:
# BAD def format(value) do cond do is_binary(value) -> String.trim(value) is_integer(value) -> Integer.to_string(value) true -> inspect(value) end end # GOOD def format(value) when is_binary(value), do: String.trim(value) def format(value) when is_integer(value), do: Integer.to_string(value) def format(value), do: inspect(value)
2. @impl true
on all behaviour callbacks
@impl trueEvery callback function MUST have
@impl true. This catches typos in function names at compile time and makes it obvious which functions are callbacks vs custom logic.
# BAD def mount(_params, _session, socket) do {:ok, socket} end # GOOD @impl true def mount(_params, _session, socket) do {:ok, socket} end
Applies to:
mount/3, render/1, handle_event/3, handle_info/2, handle_params/2, init/1, handle_call/3, handle_cast/2, terminate/2, and all other behaviour callbacks.
3. Tagged tuples for all fallible functions
Return
{:ok, result} or {:error, reason}. Never bare values that might be nil.
# BAD def find_user(id) do Repo.get(User, id) # returns nil on not found end # GOOD def find_user(id) do case Repo.get(User, id) do nil -> {:error, :not_found} user -> {:ok, user} end end
4. with
for railway-oriented composition
withChain 2+ fallible operations using
with. Normalize errors in private wrapper functions, not in else blocks.
# BAD def create_order(params) do case validate(params) do {:ok, validated} -> case charge_payment(validated) do {:ok, payment} -> case save_order(validated, payment) do {:ok, order} -> {:ok, order} error -> error end error -> error end error -> error end end # GOOD def create_order(params) do with {:ok, validated} <- validate(params), {:ok, payment} <- charge_payment(validated), {:ok, order} <- save_order(validated, payment) do {:ok, order} end end
5. Pipe operator for sequential transformations
2+ transformations use pipes. The first argument is always the data being transformed.
# BAD def process(raw_data) do trimmed = String.trim(raw_data) downcased = String.downcase(trimmed) String.replace(downcased, " ", "-") end # GOOD def process(raw_data) do raw_data |> String.trim() |> String.downcase() |> String.replace(" ", "-") end
6. Naming conventions
suffix for boolean-returning functions:?
,active?/1valid?/1
suffix for functions that raise on error:!
,fetch!/1create!/1
prefix for unused variables:_
,_params_opts- Expressive variable names:
notuser_params
,params
not_record_
# BAD def check(user), do: user.active def get(id), do: Repo.get!(User, id) def process(_, data), do: transform(data) # GOOD def active?(user), do: user.active def get_user!(id), do: Repo.get!(User, id) def process(_user, data), do: transform(data)
7. Assertive data access
Use
struct.field for required keys (fails fast). Use map[:key] only for truly optional keys. Pattern match to destructure and validate simultaneously.
# BAD -- defensive access on required fields name = user[:name] email = user[:email] # GOOD -- assertive access on required fields name = user.name email = user.email # GOOD -- pattern match to destructure %{name: name, email: email} = user # GOOD -- optional key access (key genuinely might not exist) nickname = user[:nickname]
8. No debug artifacts in committed code
No
IO.inspect/2, no dbg(), no IO.puts for debugging. Use dbg() during development (better pipeline visibility than IO.inspect), but never commit either.
# BAD -- committed to source def process(data) do data |> transform() |> IO.inspect(label: "after transform") |> finalize() end # GOOD -- clean committed code def process(data) do data |> transform() |> finalize() end