# Elixir Part 1: Core Language & Modern Idioms ## Elixir 1.19 — What's New ### Gradual Set-Theoretic Type System Elixir 1.19 significantly advances the built-in type system. It is **sound**, **gradual**, and **set-theoretic** (types compose via union, intersection, negation). **Current capabilities (1.19):** - Type inference from existing code — no annotations required yet - **Protocol dispatch type checking** — warns on invalid protocol usage (e.g., string interpolation on structs without `String.Chars`) - **Anonymous function type inference** — `fn` literals and `&captures` propagate types - Types: `atom()`, `binary()`, `integer()`, `float()`, `pid()`, `port()`, `reference()`, `tuple()`, `list()`, `map()`, `function()` - Compose: `atom() or integer()` (union), `atom() and not nil` (difference) - `dynamic()` type for gradual typing — runtime-checked values - Tuple precision: `{:ok, binary()}`, open tuples: `{:ok, binary(), ...}` - Map types: `%{key: value}` closed, `%{optional(atom()) => term()}` open - Function types: `(integer() -> boolean())` ### Up to 4x Faster Compilation 1. **Lazy module loading** — modules no longer loaded when defined; parallel compiler controls loading 2. **Parallel dependency compilation** — set `MIX_OS_DEPS_COMPILE_PARTITION_COUNT` env var **Caveat:** If spawning processes during compilation that invoke sibling modules, use `Code.ensure_compiled!/1` first. ### Other 1.19 Features - `min/2` and `max/2` allowed in guards - `Access.values/0` — traverse all values in a map/keyword - `String.count/2` — count occurrences - Unicode 17.0.0, multi-line IEx prompts - `mix help Mod.fun/arity` - Erlang/OTP 28 support --- ## Breaking Changes Since 1.15 ### Struct Update Syntax (1.18+) ```elixir # The compiler now verifies the variable matches the struct type %URI{my_uri | path: "/new"} # my_uri must be verified as %URI{} ``` ### Regex as Struct Field Defaults (OTP 28) ```elixir # BROKEN on OTP 28 — regex can't be struct field defaults defstruct pattern: ~r/foo/ # Compile error # FIX: use nil default, set at runtime ``` ### Logger Backends Deprecated ```elixir # OLD: config :logger, backends: [:console] # NEW: use LoggerHandler (Erlang's logger) config :logger, :default_handler, [] ``` ### Mix Task Separator ```elixir # OLD: mix do compile, test # NEW: mix do compile + test ``` ### mix cli/0 Replaces Config Keys ```elixir # OLD: default_task, preferred_cli_env in project/0 # NEW: def cli, do: [default_task: "phx.server", preferred_envs: [test: :test]] ``` --- ## Core Language Patterns ### The Pipeline ```elixir orders |> Enum.filter(&(&1.status == :pending)) |> Enum.sort_by(& &1.created_at, DateTime) |> Enum.map(&process_order/1) ``` ### Pattern Matching ```elixir # Function head matching — preferred over conditionals def process(%Order{status: :pending} = order), do: ship(order) def process(%Order{status: :shipped} = order), do: track(order) def process(%Order{status: :delivered}), do: :noop # Pin operator expected = "hello" ^expected = some_function() # Partial map matching %{name: name} = %{name: "Michael", age: 42} ``` ### With Expressions ```elixir with {:ok, user} <- fetch_user(id), {:ok, account} <- fetch_account(user), {:ok, balance} <- check_balance(account) do {:ok, balance} else {:error, :not_found} -> {:error, "User not found"} error -> {:error, "Unknown: #{inspect(error)}"} end ``` ### Structs and Protocols ```elixir defmodule Money do defstruct [:amount, :currency] defimpl String.Chars do def to_string(%Money{amount: a, currency: c}), do: "#{a} #{c}" end end ``` ### Behaviours ```elixir defmodule PaymentProvider do @callback charge(integer(), String.t()) :: {:ok, String.t()} | {:error, term()} @callback refund(String.t()) :: {:ok, term()} | {:error, term()} end defmodule Stripe do @behaviour PaymentProvider @impl true def charge(amount, currency), do: # ... @impl true def refund(transaction_id), do: # ... end ``` --- ## Mix Project Structure ``` my_app/ ├── config/ │ ├── config.exs # Compile-time config │ ├── dev.exs / prod.exs / test.exs │ └── runtime.exs # Runtime config (secrets, env vars) ├── lib/ │ ├── my_app/ │ │ ├── application.ex # OTP Application + supervisor tree │ │ └── ... # Domain modules │ └── my_app_web/ # Phoenix web layer (if applicable) ├── priv/repo/migrations/ ├── test/ ├── mix.exs ├── .formatter.exs └── AGENTS.md # Generated by usage_rules ``` --- ## Testing ```elixir defmodule MyApp.BlogTest do use MyApp.DataCase, async: true describe "creating posts" do test "creates with valid attributes" do assert {:ok, post} = Blog.create_post(%{title: "Hi", body: "World"}) assert post.title == "Hi" end test "fails without title" do assert {:error, changeset} = Blog.create_post(%{body: "World"}) assert "can't be blank" in errors_on(changeset).title end end end ``` Use `async: true` wherever possible. Use `Mox` for behaviour-based mocking. --- ## usage_rules — AI Agent Documentation **Always include in every project.** ```elixir # mix.exs deps {:usage_rules, "~> 1.1", only: [:dev]} # mix.exs project config usage_rules: [packages: :all, output: :agents_md, mode: :linked] ``` ```bash mix usage_rules.gen # Generate AGENTS.md from deps mix usage_rules.search_docs # Search hex documentation mix usage_rules.gen_skill # Generate SKILL.md for Cowork ``` --- ## Pro Tip: Local Docs Elixir packages ship excellent local documentation. Once Elixir is installed on cortex, accessing docs locally via `mix hex.docs fetch ` or `h Module.function` in IEx may be more efficient than fetching URLs. Consider installing Elixir on cortex to enable this workflow.