jonnifyelixir

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 |> map(&(&1 + 1)) |> filter(even?) |> reduce(+) 1 2 3 4 5 6

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

percent complete 100% lessons ada has finished all five lessons

      

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.

F4.10.1 · the request lifecycle
Idiomatic patterns
A with chain threads validate → authenticate → load → authorize, short-circuiting to the right status on the first {:error, _} — no nested cases.
F4.10.2 · the activity feed
Streams & pipelines
A lazy Stream filters and maps completions in one fused pass and stops as soon as take/2 is satisfied, instead of materialising the whole collection.
F4.10.3 · choosing a lookup
Profiling & complexity
Why the Portal keeps sessions in the F4.09 store and not a list: an 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

Related in this course