209 lines
5.8 KiB
Markdown
209 lines
5.8 KiB
Markdown
# 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 <package>` or `h Module.function` in IEx may be more efficient than fetching URLs. Consider installing Elixir on cortex to enable this workflow.
|