用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/oimiragieo/agent-studio --skill elixir-expert命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
基于 SOC 职业分类
正在显示 SKILL.md
| name | elixir-expert |
| version | 1.1.0 |
| verified | true |
| lastVerifiedAt | 2026-03-01 |
| category | Languages |
| agents | ["developer"] |
| tags | ["elixir","phoenix","erlang","functional","otp"] |
| description | Elixir and Phoenix expert including OTP, Ecto, and functional programming |
| model | sonnet |
| invoked_by | both |
| user_invocable | true |
| tools | ["Read","Write","Edit","Bash","Grep","Glob"] |
| consolidated_from | 1 skills |
| best_practices | ["Follow domain-specific conventions","Apply patterns consistently","Prioritize type safety and testing"] |
| error_handling | graceful |
| streaming | supported |
| source | builtin |
| trust_score | 100 |
| provenance_sha | 9ea3a61c1e3fa4e8 |
When reviewing or writing code, apply these guidelines:
Pattern matching is fundamental to Elixir. Use it for:
Function clauses:
def greet(%User{name: name, role: :admin}), do: "Hello Admin #{name}"
def greet(%User{name: name}), do: "Hello #{name}"
def greet(_), do: "Hello stranger"
Case statements:
case {status, data} do
{:ok, %{id: id} } when id > 0 -> process(id)
{:error, reason} -> handle_error(reason)
_ -> :unknown
end
With statements for chaining:
with {:ok, user} <- fetch_user(id),
{:ok, profile} <- fetch_profile(user),
{:ok, settings} <- fetch_settings(profile) do
{:ok, %{user: user, profile: profile, settings: settings} }
end
Use guards to add constraints to pattern matching:
def categorize(n) when is_integer(n) and n > 0, do: :positive
def categorize(n) when is_integer(n) and n < 0, do: :negative
def categorize(n) when is_integer(n), do: :zero
def process_map(map) when map_size(map) == 0, do: :empty
def process_map(map) when is_map(map), do: :has_data
The pipe operator |> improves readability:
# Instead of nested calls
result = String.trim(String.downcase(String.reverse(input)))
# Use pipes
result =
input
|> String.reverse()
|> String.downcase()
|> String.trim()
Pipe into case for handling results:
user_id
|> fetch_user()
|> case do
{:ok, user} -> process_user(user)
{:error, :not_found} -> create_user()
{:error, reason} -> {:error, reason}
end
GenServer is the foundation for stateful processes:
defmodule Counter do
use GenServer
# Client API
def start_link(initial_value) do
GenServer.start_link(__MODULE__, initial_value, name: __MODULE__)
end
def increment do
GenServer.cast(__MODULE__, :increment)
end
def get do
GenServer.call(__MODULE__, :get)
end
# Server Callbacks
@impl true
def init(initial_value) do
{:ok, initial_value}
end
@impl true
def handle_cast(:increment, state) do
{:noreply, state + 1}
end
@impl true
def handle_call(:get, _from, state) do
{:reply, state, state}
end
end
Supervisors manage process lifecycles:
defmodule MyApp.Application do
use Application
@impl true
def start(_type, _args) do
children = [
# Database
MyApp.Repo,
# PubSub
{Phoenix.PubSub, name: MyApp.PubSub},
# GenServers
{MyApp.Cache, []},
{MyApp.Worker, []},
# Endpoint (starts web server)
MyAppWeb.Endpoint
]
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
end
end
Supervisor strategies:
:one_for_one - restart only failed child:one_for_all - restart all children if one fails:rest_for_one - restart failed child and those started after itFor simple state management:
{:ok, agent} = Agent.start_link(fn -> %{} end)
Agent.update(agent, fn state -> Map.put(state, :key, "value") end)
Agent.get(agent, fn state -> Map.get(state, :key) end)
defmodule MyAppWeb.UserController do
use MyAppWeb, :controller
def index(conn, _params) do
users = Accounts.list_users()
render(conn, :index, users: users)
end
def create(conn, %{"user" => user_params}) do
case Accounts.create_user(user_params) do
{:ok, user} ->
conn
|> put_flash(:info, "User created successfully")
|> redirect(to: ~p"/users/#{user}")
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, :new, changeset: changeset)
end
end
end
For real-time interactive UIs:
defmodule MyAppWeb.CounterLive do
use MyAppWeb, :live_view
@impl true
def mount(_params, _session, socket) do
{:ok, assign(socket, count: 0)}
end
@impl true
def handle_event("increment", _params, socket) do
{:noreply, update(socket, :count, &(&1 + 1))}
end
@impl true
def render(assigns) do
~H"""
<div>
<h1>Count: <%= @count %></h1>
<button phx-click="increment">+</button>
</div>
"""
end
end
PubSub for broadcasting:
# Subscribe
Phoenix.PubSub.subscribe(MyApp.PubSub, "updates")
# Broadcast
Phoenix.PubSub.broadcast(MyApp.PubSub, "updates", {:new_data, data})
# Handle in LiveView
@impl true
def handle_info({:new_data, data}, socket) do
{:noreply, assign(socket, :data, data)}
end
For WebSocket communication:
defmodule MyAppWeb.RoomChannel do
use MyAppWeb, :channel
@impl true
def join("room:" <> room_id, _payload, socket) do
{:ok, assign(socket, :room_id, room_id)}
end
@impl true
def handle_in("new_message", %{"body" => body}, socket) do
broadcast!(socket, "new_message", %{body: body})
{:reply, :ok, socket}
end
end
defmodule MyApp.Accounts.User do
use Ecto.Schema
import Ecto.Changeset
schema "users" do
field :name, :string
field :email, :string
field :age, :integer
has_many :posts, MyApp.Content.Post
timestamps()
end
def changeset(user, attrs) do
user
|> cast(attrs, [:name, :email, :age])
|> validate_required([:name, :email])
|> validate_format(:email, ~r/@/)
|> validate_number(:age, greater_than: 0)
|> unique_constraint(:email)
end
end
import Ecto.Query
# Basic queries
query = from u in User, where: u.age > 18, select: u
# Composable queries
def for_age(query, age) do
from u in query, where: u.age > ^age
end
def ordered(query) do
from u in query, order_by: [desc: u.inserted_at]
end
# Chain them
User
|> for_age(18)
|> ordered()
|> Repo.all()
# Joins and preloads
from u in User,
join: p in assoc(u, :posts),
where: p.published == true,
preload: [posts: p]
Repo.transaction(fn ->
with {:ok, user} <- create_user(params),
{:ok, profile} <- create_profile(user),
{:ok, _settings} <- create_settings(user) do
user
else
{:error, reason} -> Repo.rollback(reason)
end
end)
defmodule MyApp.AccountsTest do
use MyApp.DataCase, async: true
describe "create_user/1" do
test "creates user with valid attributes" do
attrs = %{name: "John", email: "john@example.com"}
assert {:ok, user} = Accounts.create_user(attrs)
assert user.name == "John"
end
test "returns error with invalid email" do
attrs = %{name: "John", email: "invalid"}
assert {:error, changeset} = Accounts.create_user(attrs)
assert %{email: ["has invalid format"]} = errors_on(changeset)
end
end
end
# Testing LiveView
defmodule MyAppWeb.CounterLiveTest do
use MyAppWeb.ConnCase
import Phoenix.LiveViewTest
test "increments counter", %{conn: conn} do
{:ok, view, _html} = live(conn, "/counter")
assert view |> element("button") |> render_click() =~ "Count: 1"
end
end
Use Elixir releases for production:
# mix.exs
def project do
[
releases: [
myapp: [
include_executables_for: [:unix],
steps: [:assemble, :tar]
]
]
]
end
Build and deploy:
MIX_ENV=prod mix release
_build/prod/rel/myapp/bin/myapp start
# config/runtime.exs
import Config
if config_env() == :prod do
database_url = System.get_env("DATABASE_URL") ||
raise "DATABASE_URL not available"
config :myapp, MyApp.Repo,
url: database_url,
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")
end
# In your router
get "/health", HealthController, :check
# Controller
def check(conn, _params) do
case Repo.query("SELECT 1") do
{:ok, _} -> send_resp(conn, 200, "ok")
_ -> send_resp(conn, 503, "database unavailable")
end
end
This expert skill consolidates 1 individual skills:
Enum functions on potentially large streams — use Stream for lazy evaluation to avoid loading entire collections into memory.iex> examples in @doc) for public functions — doctests are runnable specifications; they document behavior and serve as regression tests.| Anti-Pattern | Why It Fails | Correct Approach |
|---|---|---|
| Nested if/cond chains instead of pattern matching | Harder to read; misses the power of Elixir's pattern matching; doesn't scale | Use function clauses with pattern matching heads and guard clauses |
Bare spawn without supervision | Crashed processes disappear silently; no restart, no visibility | Always use Supervisor trees; use Task.Supervisor for dynamic tasks |
Enum.map/filter on large streams | Loads entire collection into memory; causes OOM on large datasets | Use Stream.map/filter for lazy, memory-efficient pipeline processing |
| Global mutable state via Process dictionary | Process dictionary is implicit state; makes code unpredictable and untestable | Use Agent, GenServer, or ETS explicitly when shared state is required |
| No doctests for public functions | Public API has no runnable specification; behavior drifts from documentation | Always add iex> examples in @doc; run with mix test |
Before starting:
cat .claude/context/memory/learnings.md
After completing: Record any new patterns or exceptions discovered.
ASSUME INTERRUPTION: Your context may reset. If it's not in memory, it didn't happen.