5.8 KiB
5.8 KiB
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 —
fnliterals and&capturespropagate 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
- Lazy module loading — modules no longer loaded when defined; parallel compiler controls loading
- Parallel dependency compilation — set
MIX_OS_DEPS_COMPILE_PARTITION_COUNTenv var
Caveat: If spawning processes during compilation that invoke sibling modules, use Code.ensure_compiled!/1 first.
Other 1.19 Features
min/2andmax/2allowed in guardsAccess.values/0— traverse all values in a map/keywordString.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+)
# 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)
# 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
# OLD: config :logger, backends: [:console]
# NEW: use LoggerHandler (Erlang's logger)
config :logger, :default_handler, []
Mix Task Separator
# OLD: mix do compile, test
# NEW: mix do compile + test
mix cli/0 Replaces Config Keys
# 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
orders
|> Enum.filter(&(&1.status == :pending))
|> Enum.sort_by(& &1.created_at, DateTime)
|> Enum.map(&process_order/1)
Pattern Matching
# 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
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
defmodule Money do
defstruct [:amount, :currency]
defimpl String.Chars do
def to_string(%Money{amount: a, currency: c}), do: "#{a} #{c}"
end
end
Behaviours
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
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.
# mix.exs deps
{:usage_rules, "~> 1.1", only: [:dev]}
# mix.exs project config
usage_rules: [packages: :all, output: :agents_md, mode: :linked]
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 <package> or h Module.function in IEx may be more efficient than fetching URLs. Consider installing Elixir on cortex to enable this workflow.