elixir-expert

Expert guidance for Elixir programming, Phoenix framework, OTP, and building highly concurrent, fault-tolerant systems.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "elixir-expert" with this command: npx skills add personamanagmentlayer/pcl/personamanagmentlayer-pcl-elixir-expert

Elixir Expert

Expert guidance for Elixir programming, Phoenix framework, OTP, and building highly concurrent, fault-tolerant systems.

Core Concepts

Elixir Fundamentals

  • Functional programming

  • Pattern matching

  • Immutability

  • Pipe operator

  • Modules and functions

  • Structs and maps

OTP (Open Telecom Platform)

  • GenServer

  • Supervisors

  • Applications

  • Tasks

  • Agents

  • ETS (Erlang Term Storage)

Phoenix Framework

  • Contexts and schemas

  • LiveView

  • Channels (WebSockets)

  • Plug

  • Ecto (database)

  • Testing

Elixir Basics

Pattern matching

{:ok, result} = {:ok, 42} [head | tail] = [1, 2, 3, 4]

Functions

defmodule Math do def add(a, b), do: a + b

def factorial(0), do: 1 def factorial(n) when n > 0, do: n * factorial(n - 1) end

Pipe operator

"hello" |> String.upcase() |> String.reverse()

"OLLEH"

Anonymous functions

add = fn a, b -> a + b end add.(1, 2) # 3

Capture operator

add = &(&1 + &2) Enum.map([1, 2, 3], &(&1 * 2))

Structs

defmodule User do defstruct [:id, :name, :email, age: 0] end

user = %User{id: "123", name: "John", email: "john@example.com"}

Pattern matching with structs

%User{name: name} = user

Maps

user_map = %{id: "123", name: "John", email: "john@example.com"} %{name: name} = user_map

Conditionals

if user.age >= 18 do "Adult" else "Minor" end

Case

case user.role do :admin -> "Administrator" :user -> "Regular user" _ -> "Unknown" end

Cond

cond do user.age < 13 -> "Child" user.age < 18 -> "Teen" user.age < 65 -> "Adult" true -> "Senior" end

With

with {:ok, user} <- fetch_user(id), {:ok, posts} <- fetch_posts(user.id) do {:ok, user, posts} else {:error, reason} -> {:error, reason} end

GenServer

defmodule UserCache do use GenServer

Client API

def start_link(opts \ []) do GenServer.start_link(MODULE, %{}, opts) end

def get(server, key) do GenServer.call(server, {:get, key}) end

def put(server, key, value) do GenServer.cast(server, {:put, key, value}) end

Server Callbacks

@impl true def init(initial_state) do {:ok, initial_state} end

@impl true def handle_call({:get, key}, _from, state) do {:reply, Map.get(state, key), state} end

@impl true def handle_cast({:put, key, value}, state) do {:noreply, Map.put(state, key, value)} end

@impl true def handle_info(:cleanup, state) do # Periodic cleanup {:noreply, state} end end

Usage

{:ok, cache} = UserCache.start_link() UserCache.put(cache, :user1, %User{name: "John"}) UserCache.get(cache, :user1)

Supervisors

defmodule MyApp.Application do use Application

@impl true def start(_type, _args) do children = [ # Database connection pool {MyApp.Repo, []},

  # GenServer workers
  {UserCache, name: UserCache},
  {SessionManager, name: SessionManager},

  # Task supervisor
  {Task.Supervisor, name: MyApp.TaskSupervisor},

  # Phoenix endpoint
  MyAppWeb.Endpoint
]

opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)

end end

Custom supervisor

defmodule WorkerSupervisor do use Supervisor

def start_link(init_arg) do Supervisor.start_link(MODULE, init_arg, name: MODULE) end

@impl true def init(_init_arg) do children = [ {Worker, id: :worker1, restart: :permanent}, {Worker, id: :worker2, restart: :temporary}, {Worker, id: :worker3, restart: :transient} ]

Supervisor.init(children, strategy: :one_for_one)

end end

Phoenix Framework

Router

defmodule MyAppWeb.Router do use MyAppWeb, :router

pipeline :api do plug :accepts, ["json"] end

scope "/api", MyAppWeb do pipe_through :api

resources "/users", UserController, except: [:new, :edit]
get "/users/:id/posts", UserController, :posts

end end

Controller

defmodule MyAppWeb.UserController do use MyAppWeb, :controller

alias MyApp.Accounts alias MyApp.Accounts.User

def index(conn, params) do users = Accounts.list_users(params) render(conn, "index.json", users: users) end

def show(conn, %{"id" => id}) do case Accounts.get_user(id) do nil -> conn |> put_status(:not_found) |> json(%{error: "User not found"})

  user ->
    render(conn, "show.json", user: user)
end

end

def create(conn, %{"user" => user_params}) do case Accounts.create_user(user_params) do {:ok, user} -> conn |> put_status(:created) |> put_resp_header("location", Routes.user_path(conn, :show, user)) |> render("show.json", user: user)

  {:error, changeset} ->
    conn
    |> put_status(:unprocessable_entity)
    |> render(MyAppWeb.ChangesetView, "error.json", changeset: changeset)
end

end end

Context

defmodule MyApp.Accounts do import Ecto.Query alias MyApp.Repo alias MyApp.Accounts.User

def list_users(params \ %{}) do User |> apply_filters(params) |> Repo.all() end

def get_user(id), do: Repo.get(User, id)

def create_user(attrs) do %User{} |> User.changeset(attrs) |> Repo.insert() end

def update_user(%User{} = user, attrs) do user |> User.changeset(attrs) |> Repo.update() end

defp apply_filters(query, params) do Enum.reduce(params, query, fn {"name", name}, query -> from u in query, where: ilike(u.name, ^"%#{name}%")

  {"age_min", age}, query ->
    from u in query, where: u.age >= ^age

  _, query ->
    query
end)

end end

Ecto

Schema

defmodule MyApp.Accounts.User do use Ecto.Schema import Ecto.Changeset

schema "users" do field :name, :string field :email, :string field :age, :integer field :role, Ecto.Enum, values: [:admin, :user, :guest]

has_many :posts, MyApp.Blog.Post

timestamps()

end

def changeset(user, attrs) do user |> cast(attrs, [:name, :email, :age, :role]) |> validate_required([:name, :email]) |> validate_format(:email, ~r/@/) |> validate_number(:age, greater_than: 0, less_than: 150) |> unique_constraint(:email) end end

Queries

import Ecto.Query

Simple query

Repo.all(from u in User, where: u.age > 18)

With preload

Repo.all(from u in User, preload: [:posts])

Complex query

query = from u in User, join: p in assoc(u, :posts), where: u.age > 18, group_by: u.id, having: count(p.id) > 5, select: {u, count(p.id)}

Repo.all(query)

Dynamic queries

defmodule UserQueries do import Ecto.Query

def base_query, do: from(u in User)

def filter_by_age(query, min_age) do from u in query, where: u.age >= ^min_age end

def filter_by_role(query, role) do from u in query, where: u.role == ^role end

def with_posts(query) do from u in query, preload: [:posts] end end

Usage

User |> UserQueries.base_query() |> UserQueries.filter_by_age(18) |> UserQueries.filter_by_role(:admin) |> UserQueries.with_posts() |> Repo.all()

Phoenix LiveView

defmodule MyAppWeb.UserLive.Index do use MyAppWeb, :live_view

alias MyApp.Accounts

@impl true def mount(_params, _session, socket) do if connected?(socket) do Phoenix.PubSub.subscribe(MyApp.PubSub, "users") end

{:ok, assign(socket, users: Accounts.list_users())}

end

@impl true def handle_event("delete", %{"id" => id}, socket) do user = Accounts.get_user!(id) {:ok, _} = Accounts.delete_user(user)

{:noreply, assign(socket, users: Accounts.list_users())}

end

@impl true def handle_info({:user_created, user}, socket) do {:noreply, update(socket, :users, fn users -> [user | users] end)} end

@impl true def render(assigns) do ~H""" <div> <h1>Users</h1> <table> <thead> <tr> <th>Name</th> <th>Email</th> <th>Actions</th> </tr> </thead> <tbody> <%= for user <- @users do %> <tr> <td><%= user.name %></td> <td><%= user.email %></td> <td> <button phx-click="delete" phx-value-id={user.id}> Delete </button> </td> </tr> <% end %> </tbody> </table> </div> """ end end

Concurrency

Tasks

task = Task.async(fn -> expensive_computation() end) result = Task.await(task)

Parallel processing

results = [1, 2, 3, 4, 5] |> Enum.map(&Task.async(fn -> process(&1) end)) |> Enum.map(&Task.await/1)

Task supervisor

Task.Supervisor.async_nolink(MyApp.TaskSupervisor, fn -> perform_background_job() end)

Agents

{: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)

ETS

table = :ets.new(:my_table, [:set, :public]) :ets.insert(table, {:key, "value"}) :ets.lookup(table, :key)

Best Practices

Elixir

  • Use pattern matching extensively

  • Leverage pipe operator

  • Write small, focused functions

  • Handle all pattern match cases

  • Use proper error handling

  • Follow naming conventions

  • Write documentation

OTP

  • Use GenServers for state

  • Implement supervisors properly

  • Design for failure

  • Keep processes focused

  • Use process monitoring

  • Implement timeouts

  • Log appropriately

Phoenix

  • Follow context boundaries

  • Use changesets for validation

  • Implement proper authentication

  • Handle errors gracefully

  • Test thoroughly

  • Use LiveView for interactivity

  • Optimize queries

Anti-Patterns

❌ Using mutable state ❌ Long-running GenServer calls ❌ Not supervising processes ❌ Ignoring errors ❌ Large modules ❌ N+1 query problems ❌ Not using pattern matching

Resources

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

python-expert

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

devops-expert

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

code-review-expert

No summary provided by upstream source.

Repository SourceNeeds Review