Smithery Logo
MCPsSkillsDocsPricing
Login
Smithery Logo

Accelerating the Agent Economy

Resources

DocumentationPrivacy PolicySystem Status

Company

PricingAboutBlog

Connect

© 2026 Smithery. All rights reserved.

    georgeguimaraes

    elixir-thinking

    georgeguimaraes/elixir-thinking
    Coding
    85
    4 installs

    About

    SKILL.md

    Install

    Install via Skills CLI

    or add to your agent
    • Claude Code
      Claude Code
    • Codex
      Codex
    • OpenClaw
      OpenClaw
    • Cursor
      Cursor
    • Amp
      Amp
    • GitHub Copilot
      GitHub Copilot
    • Gemini CLI
      Gemini CLI
    • Kilo Code
      Kilo Code
    • Junie
      Junie
    • Replit
      Replit
    • Windsurf
      Windsurf
    • Cline
      Cline
    • Continue
      Continue
    • OpenCode
      OpenCode
    • OpenHands
      OpenHands
    • Roo Code
      Roo Code
    • Augment
      Augment
    • Goose
      Goose
    • Trae
      Trae
    • Zencoder
      Zencoder
    • Antigravity
      Antigravity
    ├─
    ├─
    └─

    About

    This skill should be used when the user asks to "implement a feature in Elixir", "refactor this module", "should I use a GenServer here?", "how should I structure this?", "use the pipe operator",...

    SKILL.md

    Elixir Thinking

    Mental shifts required before writing Elixir. These contradict conventional OOP patterns.

    The Iron Law

    NO PROCESS WITHOUT A RUNTIME REASON
    

    Before creating a GenServer, Agent, or any process, answer YES to at least one:

    1. Do I need mutable state persisting across calls?
    2. Do I need concurrent execution?
    3. Do I need fault isolation?

    All three are NO? Use plain functions. Modules organize code; processes manage runtime.

    The Three Decoupled Dimensions

    OOP couples behavior, state, and mutability together. Elixir decouples them:

    OOP Dimension Elixir Equivalent
    Behavior Modules (functions)
    State Data (structs, maps)
    Mutability Processes (GenServer)

    Pick only what you need. "I only need data and functions" = no process needed.

    "Let It Crash" = "Let It Heal"

    The misconception: Write careless code. The truth: Supervisors START processes.

    • Handle expected errors explicitly ({:ok, _} / {:error, _})
    • Let unexpected errors crash → supervisor restarts

    Control Flow

    Pattern matching first:

    • Match on function heads instead of if/else or case in bodies
    • %{} matches ANY map—use map_size(map) == 0 guard for empty maps
    • Avoid nested case—refactor to single case, with, or separate functions

    Error handling:

    • Use {:ok, result} / {:error, reason} for operations that can fail
    • Avoid raising exceptions for control flow
    • Use with for chaining {:ok, _} / {:error, _} operations

    Be explicit about expected cases:

    • Avoid _ -> nil catch-alls—they silently swallow unexpected cases
    • Avoid value && value.field nil-punning—obscures actual return types
    • When a case has {:ok, nil} -> nil alongside {:ok, value} -> value.field, use with instead:
    # Verbose
    case get_run(id) do
      {:ok, nil} -> nil
      {:ok, run} -> run.recommendations
    end
    
    # Prefer
    with {:ok, %{recommendations: recs}} <- get_run(id), do: recs
    

    Polymorphism

    For Polymorphism Over... Use Contract
    Modules Behaviors Upfront callbacks
    Data Protocols Upfront implementations
    Processes Message passing Implicit (send/receive)

    Behaviors = default for module polymorphism (very cheap at runtime) Protocols = only when composing data types, especially built-ins Message passing = only when stateful by design (IO, file handles)

    Use the simplest abstraction: pattern matching → anonymous functions → behaviors → protocols → message passing. Each step adds complexity.

    When justified: Library extensibility, multiple implementations, test swapping. When to stay coupled: Internal module, single implementation, pattern matching handles all cases.

    Data Modeling Replaces Class Hierarchies

    OOP: Complex class hierarchy + visitor pattern. Elixir: Model as data + pattern matching + recursion.

    {:sequence, {:literal, "rain"}, {:repeat, {:alternation, "dogs", "cats"}}}
    
    def interpret({:literal, text}, input), do: ...
    def interpret({:sequence, left, right}, input), do: ...
    def interpret({:repeat, pattern}, input), do: ...
    

    Defaults and Options

    Use /3 variants (Keyword.get/3, Map.get/3) instead of case statements branching on nil:

    # WRONG
    case Keyword.get(opts, :chunker) do
      nil -> chunker()
      config -> parse_chunker_config(config)
    end
    
    # RIGHT
    Keyword.get(opts, :chunker, :default) |> parse_chunker_config()
    

    Don't create helper functions to merge config defaults. Inline the fallback:

    # WRONG
    defp merge_defaults(opts), do: Keyword.merge([repo: Application.get_env(:app, :repo)], opts)
    
    # RIGHT
    def some_function(opts) do
      repo = opts[:repo] || Application.get_env(:app, :repo)
    end
    

    Idioms

    • Process dictionary is typically unidiomatic—pass state explicitly
    • Reserve is_thing names for guards only
    • Use structs over maps when shape is known: defstruct [:name, :age]
    • Prepend to lists [new | list] not list ++ [new]
    • Use dbg/1 for debugging—prints formatted value with context
    • Use built-in JSON module (Elixir 1.18+) instead of Jason

    Testing

    Always prefix mix commands with unbuffer to get ANSI colors and prevent stdout block-buffering in non-TTY environments (e.g. unbuffer mix test). Install: brew install expect (macOS) or apt install expect (Linux).

    Prefer pattern matching over imperative assertions. Never use assert length + Enum.at/List.last/hd. Pattern match checks length and content in one shot:

    # Bad
    assert length(students) == 2
    assert Enum.at(students, 0).name == "Alice"
    assert Enum.at(students, 1).name == "Bob"
    
    # Good
    assert [%{name: "Alice"}, %{name: "Bob"}] = students
    

    Same goes for type-only predicates: assert is_map(user) / assert is_list(posts) pass for almost any non-error return. Pattern match the shape and content together: assert %User{email: "a@b.com"} = user. is_nil/1 is fine when nil-ness is the whole point.

    Test behavior, not implementation. Test use cases / public API. Refactoring shouldn't break tests.

    Test your code, not the framework. If deleting your code doesn't fail the test, it's tautological.

    Keep tests async. async: false means you've coupled to global state. Fix the coupling:

    Problem Solution
    Application.put_env Pass config as function argument
    Feature flags Inject via process dictionary or context
    ETS tables Create per-test tables with unique names
    External APIs Use Mox with explicit allowances
    File system operations Use @tag :tmp_dir (see below)

    Use tmp_dir for file tests. ExUnit creates unique temp directories per test, async-safe:

    @tag :tmp_dir
    test "writes file", %{tmp_dir: tmp_dir} do
      path = Path.join(tmp_dir, "test.txt")
      File.write!(path, "content")
      assert File.read!(path) == "content"
    end
    

    Directory is auto-cleaned before each run. Works with @moduletag :tmp_dir for all tests in module.

    Common Rationalizations

    Excuse Reality
    "I need a process to organize this code" Modules organize code. Processes are for runtime.
    "GenServer is the Elixir way" Plain functions are also the Elixir way.
    "I'll need state eventually" YAGNI. Add process when you need it.
    "It's just a simple wrapper process" Simple wrappers become bottlenecks.
    "This is how I'd structure it in OOP" Rethink from data flow.

    Red Flags - STOP and Reconsider

    • Creating process without answering the three questions
    • Using GenServer for stateless operations
    • Wrapping a library in a process "for safety"
    • One process per entity without runtime justification
    • Reaching for protocols when pattern matching works

    Any of these? Re-read The Iron Law.

    Recommended Servers
    Vercel Grep
    Vercel Grep
    Svelte
    Svelte
    Jina AI
    Jina AI
    Repository
    georgeguimaraes/claude-code-elixir
    Files