📦 deps(thirdparty): update snapshots
This commit is contained in:
@@ -0,0 +1,366 @@
|
||||
---
|
||||
name: elixir
|
||||
description: "Language-specific super-code guidelines for elixir."
|
||||
risk: safe
|
||||
source: community
|
||||
date_added: "2026-06-16"
|
||||
---
|
||||
# Elixir / Erlang: Idiomatic Efficiency Reference
|
||||
|
||||
## Table of Contents
|
||||
1. [Pattern Matching & Guards](#patterns)
|
||||
2. [Pipe Operator & Transforms](#pipes)
|
||||
3. [Processes & OTP](#otp)
|
||||
4. [Error Handling](#errors)
|
||||
5. [Collections & Enum](#collections)
|
||||
6. [Structs & Protocols](#structs)
|
||||
7. [Anti-patterns specific to Elixir/Erlang](#antipatterns)
|
||||
|
||||
---
|
||||
|
||||
## 1. Pattern Matching & Guards {#patterns}
|
||||
|
||||
```elixir
|
||||
# ❌ Extracting with Map.get then checking
|
||||
value = Map.get(map, :key)
|
||||
if value != nil do
|
||||
process(value)
|
||||
end
|
||||
|
||||
# ✅ — pattern match directly
|
||||
case map do
|
||||
%{key: value} -> process(value)
|
||||
_ -> :noop
|
||||
end
|
||||
# or with if:
|
||||
if value = map[:key], do: process(value)
|
||||
```
|
||||
|
||||
```elixir
|
||||
# ❌ Nested case for multiple conditions
|
||||
case fetch_user(id) do
|
||||
{:ok, user} ->
|
||||
case validate(user) do
|
||||
{:ok, valid_user} -> save(valid_user)
|
||||
{:error, reason} -> {:error, reason}
|
||||
end
|
||||
{:error, reason} -> {:error, reason}
|
||||
end
|
||||
|
||||
# ✅ — with clause
|
||||
with {:ok, user} <- fetch_user(id),
|
||||
{:ok, valid_user} <- validate(user) do
|
||||
save(valid_user)
|
||||
end
|
||||
```
|
||||
|
||||
```elixir
|
||||
# ❌ if/else for known shapes
|
||||
def area(shape) do
|
||||
if shape.type == :circle do
|
||||
:math.pi() * shape.radius * shape.radius
|
||||
else
|
||||
shape.width * shape.height
|
||||
end
|
||||
end
|
||||
|
||||
# ✅ — multi-clause function with pattern match
|
||||
def area(%{type: :circle, radius: r}), do: :math.pi() * r * r
|
||||
def area(%{type: :rect, width: w, height: h}), do: w * h
|
||||
```
|
||||
|
||||
```elixir
|
||||
# ❌ Checking type at runtime
|
||||
def process(x) do
|
||||
if is_integer(x) and x > 0 do
|
||||
x * 2
|
||||
end
|
||||
end
|
||||
|
||||
# ✅ — guard clause
|
||||
def process(x) when is_integer(x) and x > 0, do: x * 2
|
||||
def process(_), do: {:error, :invalid_input}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Pipe Operator & Transforms {#pipes}
|
||||
|
||||
```elixir
|
||||
# ❌ Nested function calls
|
||||
String.trim(String.downcase(String.replace(input, ~r/\s+/, " ")))
|
||||
|
||||
# ✅
|
||||
input
|
||||
|> String.replace(~r/\s+/, " ")
|
||||
|> String.downcase()
|
||||
|> String.trim()
|
||||
```
|
||||
|
||||
```elixir
|
||||
# ❌ Pipe into anonymous function awkwardly
|
||||
data
|
||||
|> (fn x -> x * 2 end).()
|
||||
|
||||
# ✅ — use then/1 or named function
|
||||
data
|
||||
|> then(&(&1 * 2))
|
||||
# or better: extract a named function
|
||||
data |> double()
|
||||
```
|
||||
|
||||
```elixir
|
||||
# ❌ Single-step pipe (no gain in readability)
|
||||
result = list |> Enum.count()
|
||||
|
||||
# ✅ — direct call for single operation
|
||||
result = Enum.count(list)
|
||||
```
|
||||
|
||||
**Pipe when 2+ transforms. Direct call for single operation. First arg flows through pipe.**
|
||||
|
||||
---
|
||||
|
||||
## 3. Processes & OTP {#otp}
|
||||
|
||||
```elixir
|
||||
# ❌ Raw spawn for stateful process
|
||||
pid = spawn(fn -> loop(%{count: 0}) end)
|
||||
send(pid, {:increment})
|
||||
|
||||
# ✅ — GenServer for stateful processes
|
||||
defmodule Counter do
|
||||
use GenServer
|
||||
|
||||
def start_link(init \\ 0), do: GenServer.start_link(__MODULE__, init)
|
||||
def increment(pid), do: GenServer.call(pid, :increment)
|
||||
|
||||
@impl true
|
||||
def init(count), do: {:ok, count}
|
||||
|
||||
@impl true
|
||||
def handle_call(:increment, _from, count), do: {:reply, count + 1, count + 1}
|
||||
end
|
||||
```
|
||||
|
||||
```elixir
|
||||
# ❌ Spawning without linking (orphan process on crash)
|
||||
spawn(fn -> do_work() end)
|
||||
|
||||
# ✅ — Task for fire-and-forget with supervision
|
||||
Task.start(fn -> do_work() end)
|
||||
# or for awaitable result:
|
||||
task = Task.async(fn -> do_work() end)
|
||||
result = Task.await(task)
|
||||
```
|
||||
|
||||
```elixir
|
||||
# ❌ Manual process registry
|
||||
Process.register(self(), :my_worker)
|
||||
|
||||
# ✅ — use Registry or named GenServer
|
||||
{:ok, _} = Registry.start_link(keys: :unique, name: MyRegistry)
|
||||
GenServer.start_link(Worker, arg, name: {:via, Registry, {MyRegistry, :my_worker}})
|
||||
```
|
||||
|
||||
```elixir
|
||||
# ❌ try/catch in GenServer (breaks supervision)
|
||||
def handle_call(:work, _from, state) do
|
||||
try do
|
||||
result = risky_operation()
|
||||
{:reply, result, state}
|
||||
catch
|
||||
_ -> {:reply, :error, state}
|
||||
end
|
||||
end
|
||||
|
||||
# ✅ — let it crash; supervisor restarts
|
||||
def handle_call(:work, _from, state) do
|
||||
result = risky_operation()
|
||||
{:reply, result, state}
|
||||
end
|
||||
```
|
||||
|
||||
**"Let it crash" — supervisors handle recovery. Don't defensively catch inside GenServers.**
|
||||
|
||||
---
|
||||
|
||||
## 4. Error Handling {#errors}
|
||||
|
||||
```elixir
|
||||
# ❌ Raising for expected failures
|
||||
def find_user(id) do
|
||||
case Repo.get(User, id) do
|
||||
nil -> raise "User not found"
|
||||
user -> user
|
||||
end
|
||||
end
|
||||
|
||||
# ✅ — tagged tuples for expected outcomes
|
||||
def find_user(id) do
|
||||
case Repo.get(User, id) do
|
||||
nil -> {:error, :not_found}
|
||||
user -> {:ok, user}
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
```elixir
|
||||
# ❌ Ignoring error tuple
|
||||
{:ok, result} = might_fail() # crashes on {:error, _}
|
||||
|
||||
# ✅ — handle both cases
|
||||
case might_fail() do
|
||||
{:ok, result} -> process(result)
|
||||
{:error, reason} -> Logger.error("Failed: #{inspect(reason)}")
|
||||
end
|
||||
```
|
||||
|
||||
```elixir
|
||||
# ❌ String errors
|
||||
{:error, "something went wrong"}
|
||||
|
||||
# ✅ — atom or struct errors (matchable, cheap)
|
||||
{:error, :timeout}
|
||||
{:error, %ValidationError{field: :email, reason: :invalid_format}}
|
||||
```
|
||||
|
||||
```elixir
|
||||
# ❌ Deep nesting of ok/error checks
|
||||
case step1() do
|
||||
{:ok, a} ->
|
||||
case step2(a) do
|
||||
{:ok, b} ->
|
||||
case step3(b) do
|
||||
{:ok, c} -> {:ok, c}
|
||||
error -> error
|
||||
end
|
||||
error -> error
|
||||
end
|
||||
error -> error
|
||||
end
|
||||
|
||||
# ✅
|
||||
with {:ok, a} <- step1(),
|
||||
{:ok, b} <- step2(a),
|
||||
{:ok, c} <- step3(b) do
|
||||
{:ok, c}
|
||||
else
|
||||
{:error, reason} -> {:error, reason}
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Collections & Enum {#collections}
|
||||
|
||||
```elixir
|
||||
# ❌ Multiple passes when one suffices
|
||||
items
|
||||
|> Enum.filter(&(&1.active))
|
||||
|> Enum.map(&(&1.name))
|
||||
|
||||
# ✅ — for comprehension when filter + transform
|
||||
for %{active: true, name: name} <- items, do: name
|
||||
```
|
||||
|
||||
```elixir
|
||||
# ❌ Enum.count for empty check (traverses whole list)
|
||||
if Enum.count(list) == 0, do: :empty
|
||||
|
||||
# ✅
|
||||
if Enum.empty?(list), do: :empty
|
||||
# or pattern match:
|
||||
case list do
|
||||
[] -> :empty
|
||||
_ -> :has_items
|
||||
end
|
||||
```
|
||||
|
||||
```elixir
|
||||
# ❌ Building map with Enum.reduce when Map.new works
|
||||
Enum.reduce(users, %{}, fn user, acc -> Map.put(acc, user.id, user) end)
|
||||
|
||||
# ✅
|
||||
Map.new(users, &{&1.id, &1})
|
||||
```
|
||||
|
||||
```elixir
|
||||
# ❌ Enum on large dataset (eager — builds intermediate lists)
|
||||
huge_list
|
||||
|> Enum.map(&transform/1)
|
||||
|> Enum.filter(&valid?/1)
|
||||
|> Enum.take(10)
|
||||
|
||||
# ✅ — Stream for lazy evaluation
|
||||
huge_list
|
||||
|> Stream.map(&transform/1)
|
||||
|> Stream.filter(&valid?/1)
|
||||
|> Enum.take(10)
|
||||
```
|
||||
|
||||
**Use `Stream` when chaining transforms on large/infinite collections. `Enum` for small or final step.**
|
||||
|
||||
---
|
||||
|
||||
## 6. Structs & Protocols {#structs}
|
||||
|
||||
```elixir
|
||||
# ❌ Plain map for domain entities
|
||||
user = %{name: "Alice", email: "a@b.com", age: 30}
|
||||
# typo in key goes unnoticed: user.emaail
|
||||
|
||||
# ✅ — struct enforces keys
|
||||
defmodule User do
|
||||
@enforce_keys [:name, :email]
|
||||
defstruct [:name, :email, age: 0]
|
||||
end
|
||||
user = %User{name: "Alice", email: "a@b.com"}
|
||||
```
|
||||
|
||||
```elixir
|
||||
# ❌ Protocol with only one implementation (over-abstraction)
|
||||
defprotocol Renderable do
|
||||
def render(data)
|
||||
end
|
||||
defimpl Renderable, for: HtmlPage do ... end
|
||||
|
||||
# ✅ — just a function until you need polymorphism
|
||||
def render(%HtmlPage{} = page), do: ...
|
||||
```
|
||||
|
||||
```elixir
|
||||
# ❌ Updating nested struct manually
|
||||
updated = %{user | address: %{user.address | city: "NYC"}}
|
||||
|
||||
# ✅
|
||||
updated = put_in(user.address.city, "NYC")
|
||||
# or Kernel.update_in/3 for transforms
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Anti-patterns specific to Elixir/Erlang {#antipatterns}
|
||||
|
||||
| Anti-pattern | Preferred |
|
||||
|---|---|
|
||||
| `spawn` without link/monitor | `Task.start_link` or `GenServer` |
|
||||
| `try/catch` inside GenServer | let it crash; supervisor restarts |
|
||||
| String error reasons | atom or struct errors |
|
||||
| `Enum.count(x) == 0` | `Enum.empty?(x)` or `match?([], x)` |
|
||||
| Mutable-style accumulator | `Enum.reduce` / recursion |
|
||||
| `if/else` chain on data shape | multi-clause function + pattern match |
|
||||
| Nested `case` for ok/error | `with` expression |
|
||||
| `IO.inspect` left in prod | `Logger` with levels |
|
||||
| Single-step pipe | direct function call |
|
||||
| `Enum` on huge/infinite data | `Stream` |
|
||||
| Raw PID passing | named processes / Registry |
|
||||
| Boolean returns for success/fail | `{:ok, val}` / `{:error, reason}` tuples |
|
||||
| `length(list) > 0` (O(n)) | pattern match `[_ | _]` |
|
||||
| Shared mutable state via ETS without wrapper | GenServer or Agent as access layer |
|
||||
|
||||
|
||||
|
||||
## Limitations
|
||||
- These are language-specific guidelines and do not cover overall architectural decisions.
|
||||
- Over-compression might reduce readability; apply judgement.
|
||||
Reference in New Issue
Block a user