F4 · Practical recipes · module 10
Practical recipes in Elixir
The spine of this chapter built the structures; this module is how the Portal actually uses them.
Three recipes recur: a with chain to thread a request through validate-load-authorize
without nested cases; a lazy Stream pipeline to build reports over large collections
without materialising every step; and reading complexity to pick the structure a lookup
deserves. None is exotic — together they are most of the Portal’s day-to-day code.
Start with the report the dashboard shows: how far a learner has progressed, counted from the progress store of F4.09. Select a learner.
An Enum pipeline, one stage at a time
input [1, 2, 3, 4, 5, 6]
Step to map each element, keep the evens, then sum what is left.
The progress report
A course has five lessons. Portal.Progress.percent_complete/1 reads a learner’s
snapshot, counts the lessons they have finished, and divides. Select a learner to compute it from the same
five-lesson course.
Learner · select one
learner: ada
complete: 5 of 5
The report is a count over an immutable snapshot and a division. The recipes below are how that stays small: validate the request once, stream the completions, and lean on a store whose lookups are cheap.
Three recipes
Each turns one part of the Portal into idiomatic Elixir, and each is a deep dive.
with chain threads validate → authenticate → load → authorize, short-circuiting to the right status on the first {:error, _} — no nested cases.Stream filters and maps completions in one fused pass and stops as soon as take/2 is satisfied, instead of materialising the whole collection.O(n) scan against an O(log₃₂ n) map lookup, read straight from the code.Advanced: recipes compose
The three are one flow more often than not. A dashboard request validates its inputs with a
with chain; on the happy path it builds the report with a lazy pipeline over the
learner’s completions; and every store touch inside that pipeline is an O(log₃₂ n)
lookup rather than a scan. The recipes are not three separate tricks but the same instinct — let the
idiom and the data structure carry the work — applied at the three layers a request passes through.
Read the function below top to bottom: the with handles the things that can fail and
names the happy path; the pipeline expresses the aggregation as a sequence of transforms; and
Store.get/1 is the branded-CHAMP lookup from F4.09. Nothing here materialises a list
it does not need, nests a case it can flatten, or scans a collection a map could index.
# one request: validate (with), aggregate (Stream), look up (O(log n)) def course_progress(raw_uid, raw_cid) do with {:ok, uid} <- Portal.Id.validate(raw_uid, :USR), {:ok, cid} <- Portal.Id.validate(raw_cid, :CRS), %{} = user <- Store.get(uid) do lessons = Catalog.lessons(cid) done = lessons |> Stream.filter(&Progress.completed?(user, &1)) |> Enum.count() {:ok, round(done / length(lessons) * 100)} end end
F4.05–F4.09 · the structures
HAMT, CHAMP, branded ids, persistence, the live store.
F4.10 · the recipes
The idioms that turn those structures into the Portal’s everyday code.
Start with idiomatic patterns, then streams & pipelines, then profiling & complexity. Predecessor: F4.09 — Branded CHAMP maps & GenServer. Next module: F4.11 — Dynamic programming in Elixir.
References
Primary sources for this lesson, and where it connects in the course.
Sources
- Elixir —
with/1— threading a happy path over fallible steps. - Elixir — Stream — lazy, composable enumerables.
- Elixir — Enum — the eager counterpart and where a pipeline terminates.
Related in this course
- F4.09 · Branded CHAMP maps & GenServer — the store these recipes read.
- F4.08 · Branded ids & persistence — the validation the
withchain reuses. - F4.04 · Maps, sets & hashing — why a map lookup beats a scan.
- F4 · Algorithms & Data Structures