8. Data Structures (pocket version)

TL;DR (5 lines)

  • Data structures are architectural choices.
  • Use cases matter more than names.
  • Aggregation flows are better teaching units than isolated declarations.
  • Broken examples should show a mismatched shape.
  • Container choice should remain visible in the code story.

Concrete Problem

Readers often see containers as names only, not as data-shape decisions embedded in a complete flow.

Coherent example

space demo/collections

form Metrics {
  count: int
  total: int
}

proc absorb(values: list[int]) -> Metrics {
  let count: int = 0
  let total: int = 0
  for value in values {
    set count = count + 1
    set total = total + value
  }
  give Metrics(count, total)
}

export *

Complete examples

Each block below is a complete reading unit with its own boundary, data shape, and observable result.

Primary coherent example

This is the compact chapter anchor used by the surrounding explanation.

space demo/collections

form Metrics {
  count: int
  total: int
}

proc absorb(values: list[int]) -> Metrics {
  let count: int = 0
  let total: int = 0
  for value in values {
    set count = count + 1
    set total = total + value
  }
  give Metrics(count, total)
}

export *

Aggregate values into a form

This program shows why grouped output deserves its own shape.

space examples/collections/aggregate

form Totals {
  count: int
  sum: int
}

proc totals(values: list[int]) -> Totals {
  let count: int = 0
  let sum: int = 0
  for value in values {
    set count = count + 1
    set sum = sum + value
  }
  give Totals { count: count, sum: sum }
}

proc main(args: list[string]) -> int {
  let result: Totals = totals([1, 2, 3])
  give result.count
}

export *

Filter before counting

This variant uses the collection because the access pattern requires iteration.

space examples/collections/filter

proc accepted(values: list[int]) -> int {
  let count: int = 0
  for value in values {
    if value < 10 { continue }
    set count = count + 1
  }
  give count
}

proc main(args: list[string]) -> int {
  give accepted([3, 10, 12])
}

export *

Immediate work for this chapter

  • Show the access pattern that justifies the collection.
  • Return a grouped result when aggregation creates a new domain shape.
  • Avoid listing containers without a flow that uses them.
  • Keep the first code block complete and aligned with current Vitte docs syntax.
  • Keep the invalid block focused on one broken contract only.
  • Replace generic prose with one concrete rule visible in the code.
  • Keep every example small enough to review from top to bottom.

Chapter override: 08-structures.html

Dedicated problem

This chapter must teach structures as data-shape decisions. The reader should understand why grouped values, collections, and aggregate results exist in a flow.

Specific complete examples

Aggregate summary

A form gives the loop result a stable shape.

space examples/structures/aggregate_summary

form Summary {
  count: int
  total: int
}

proc summarize(values: list[int]) -> Summary {
  let count: int = 0
  let total: int = 0
  for value in values {
    set count = count + 1
    set total = total + value
  }
  give Summary { count: count, total: total }
}

proc main(args: list[string]) -> int {
  let summary: Summary = summarize([1, 2, 3])
  give summary.count
}

export *

Filter shape

The collection is justified by an access pattern, not by vocabulary.

space examples/structures/filter_shape

proc count_ready(values: list[int]) -> int {
  let ready: int = 0
  for value in values {
    if value <= 0 { continue }
    set ready = ready + 1
  }
  give ready
}

proc main(args: list[string]) -> int {
  give count_ready([0, 2, 4])
}

export *

Grouped status

Related status values stay together instead of drifting across locals.

space examples/structures/grouped_status

form Status {
  code: int
  retries: int
}

proc build_status(code: int) -> Status {
  if code != 0 { give Status { code: code, retries: 0 } }
  give Status { code: 0, retries: 1 }
}

proc main(args: list[string]) -> int {
  let status: Status = build_status(0)
  give status.code
}

export *

Risks and diagnostics

RiskDiagnostic signalAction
Scattered valuesRelated values move independently and lose meaning.Group them in a form when they are reviewed together.
Container without patternA list appears without iteration, lookup, or aggregation.Name the access pattern in code and prose.
Aggregate ambiguityThe result of a loop is split across unrelated locals.Return a grouped result shape.

Review checklist

  • Every structure has a visible access pattern.
  • Aggregates return named fields.
  • The example shows where grouped data enters and leaves.
  • Invalid examples break data shape rather than spelling.

Production use

  • Use forms for stable API payloads and module boundaries.
  • Use collections when repeated access is part of the contract.
  • Keep aggregate outputs named so tests can assert each part.

What to avoid

  • Do not list containers without using them in a flow.
  • Do not keep related facts as separate unnamed integers.
  • Do not choose data structures for style when access pattern is the real reason.

Global explanation

Collections pages are useful only when they explain why a shape exists. The example shows aggregation as a real need for a container-like surface instead of presenting structures as vocabulary alone.

Invalid case

proc bad_metrics() -> int {
  let values: int = [1, 2, 3]
  give values
}

This invalid case stays small so the broken contract remains visible.

Common pitfalls

  • Listing containers without an access pattern or use case.
  • Treating structure choice as style instead of behavior.
  • Hiding where grouped data enters and leaves the block.

Short exercise

Replace the single summary form with two grouped buckets and explain why the new shape is justified.

Summary in 5 points

  1. Data structures are architectural choices.
  2. Use cases matter more than names.
  3. Aggregation flows are better teaching units than isolated declarations.
  4. Broken examples should show a mismatched shape.
  5. Container choice should remain visible in the code story.

Next best action

Keep the example small, reproduce it locally, then continue to the full chapter if you need the broader context.

Chapter deep dive

8. Data Structures teaches data shape as an engineering choice. The chapter is written for a reader choosing a container because the domain requires one.

The practical boundary is: single value, grouped values, and aggregate result. Keep that boundary in view while reading the example, the invalid case, and the exercise.

Role in the learning path

Readers often see containers as names only, not as data-shape decisions embedded in a complete flow.

One report-building flow stores values, aggregates them, and returns a grouped result.

This chapter helps the reader choose structures based on access pattern and meaning.

Profile-specific deep dive

Access pattern

  • Choose a collection because the code needs grouping, iteration, lookup, or aggregation.
  • The example should show where values enter and leave the container.
  • A collection without an access pattern is vocabulary, not design.

Aggregation result

  • Use a result form when a loop produces more than one fact.
  • Keep counters and totals visible while the aggregation runs.
  • Return the grouped result rather than scattering related values across locals.

Reading the valid example

  1. space demo/collections: names the ownership boundary before any behavior appears.
  2. form Metrics {: introduces a data contract that later branches can rely on.
  3. count: int: supports the chapter contract without adding hidden behavior.
  4. total: int: supports the chapter contract without adding hidden behavior.
  5. }: supports the chapter contract without adding hidden behavior.
  6. proc absorb(values: list[int]) -> Metrics {: states the callable contract: inputs first, result shape last.
  7. let count: int = 0: keeps an intermediate decision visible for review.
  8. let total: int = 0: keeps an intermediate decision visible for review.
  9. for value in values {: supports the chapter contract without adding hidden behavior.
  10. set count = count + 1: supports the chapter contract without adding hidden behavior.
  11. set total = total + value: supports the chapter contract without adding hidden behavior.
  12. }: supports the chapter contract without adding hidden behavior.
  13. give Metrics(count, total): ends the local path with an explicit result.
  14. }: supports the chapter contract without adding hidden behavior.
  15. export *: supports the chapter contract without adding hidden behavior.

Lesson from the invalid example

  1. proc bad_metrics() -> int {: this line helps isolate the failure because it states the callable contract: inputs first, result shape last.
  2. let values: int = [1, 2, 3]: this line helps isolate the failure because it keeps an intermediate decision visible for review.
  3. give values: this line helps isolate the failure because it ends the local path with an explicit result.
  4. }: this line helps isolate the failure because it supports the chapter contract without adding hidden behavior.

Engineering decisions to preserve

  • Name the boundary before changing code: Data structures are architectural choices.
  • Keep the smallest example executable: Use cases matter more than names.
  • Make the invalid path explain one failure only: Aggregation flows are better teaching units than isolated declarations.
  • Prefer a visible contract over an implied convention: Broken examples should show a mismatched shape.
  • Leave a review anchor that another maintainer can verify: Container choice should remain visible in the code story.

Context-specific review criteria

  • The page makes the single value, grouped values, and aggregate result boundary visible before the first code block.
  • The intended reader, a reader choosing a container because the domain requires one, can follow the valid example through named contracts instead of memorized tokens.
  • The invalid example fails for the same reason the prose discusses.
  • The exercise extends the same contract instead of introducing an unrelated concept.
  • The next chapter can reuse the vocabulary introduced here without redefining it.
  • The chapter stays specific enough that its title materially changes the meaning of the page.
  • Every warning connects to a concrete code shape.
  • The summary leaves one durable engineering rule behind.

Contract matrix

ConcernChapter ruleEvidence to keep
OwnershipCode belongs behind the boundary named by the chapter.The chapter keeps ownership visible through single value, grouped values, and aggregate result.
Input contractThe procedure receives a shape that is named before branching.The valid example names the accepted shape before branching.
Nominal pathThe clean path remains readable without hidden state.The successful result can be found without reading hidden state.
Failure pathThe invalid case isolates one failure reason.The broken example has one main reason to fail.
NamingNames explain the domain rather than only the mechanism.Names remain tied to the chapter goal.
TypesTypes remove ambiguity from values and results.Fields and return values carry domain meaning.
Control flowBranches stay traceable from guard to result.Guards appear before the result they protect.
Module boundaryThe public surface stays smaller than implementation detail.The public surface remains smaller than the implementation detail.
Diagnostic valueThe failure path points back to the exact contract.The invalid example points back to the exact contract.
Test valueRegression evidence covers one passing path and one failing path.One passing case and one failing case cover the lesson.
Refactor valueImplementation cleanup preserves the result shape.The result shape stays stable during local cleanup.
Publication valueThe chapter leaves one concrete engineering rule.The chapter leaves one concrete engineering rule.

Rewrite path for this chapter

  1. Rewrite the opening paragraph so it names single value, grouped values, and aggregate result before naming syntax.
  2. Keep the valid example small enough that the full contract fits on screen.
  3. Move any broad claim back to a specific line in the example.
  4. Preserve one invalid case that fails for the chapter's main reason.
  5. Add one sentence explaining why the invalid case is not a random error.
  6. Make every pitfall actionable by naming the code shape it damages.
  7. Keep the exercise inside the same domain as the example.
  8. Avoid introducing a second unrelated project just to show variety.
  9. Use the summary to restate the chapter rule, not the table of contents.
  10. Check that the next chapter can build on this vocabulary.
  11. Remove any sentence that would still be true in every other chapter.
  12. Keep the last action small, local, and testable.

Diagnostic anchors

  • The first inspected line is the one that declares the chapter's main contract.
  • The central type, field, procedure, or branch carries the chapter's main idea.
  • The invalid example includes a sentence-level explanation of its failure.
  • Refactors preserve the detail that would otherwise mislead a future reader.
  • The behavior that must stay stable is named before implementation changes begin.
  • Vague names are replaced before they become review friction.
  • Regression coverage protects the chapter's main contract.
  • Implementation details stay out of public API unless the chapter explicitly teaches that surface.
  • Beginner-facing diagnostics point to the contract, not to a random syntax detail.
  • The next chapter can assume one clearly named concept from this page.

When extending this chapter

  • Extend toward a reader choosing a container because the domain requires one, not toward a broader catalog of features.
  • Add a second example only if it sharpens the same contract.
  • Prefer a small variant over a new subsystem.
  • Keep prose close to code; every abstract claim should point to a visible shape.
  • Do not hide a new concept in the exercise.
  • If a paragraph explains policy, add the concrete code boundary it protects.
  • If a paragraph explains syntax, add the semantic reason the syntax matters.
  • If a paragraph explains architecture, identify the owner of each boundary.
  • If a paragraph explains failure, keep the failing line close to the explanation.
  • Stop expanding when the chapter has one complete, testable lesson.

Failure modes to avoid

  • Listing containers without an access pattern or use case.
  • Treating structure choice as style instead of behavior.
  • Hiding where grouped data enters and leaves the block.

Practice scenario

Start from the coherent example in 8. Data Structures. Change one identifier, one guard, and one returned value. After each change, write down whether the public contract is still the same contract or a new one.

If the contract changed, update the type or result shape first. If only the implementation changed, keep the external name stable and add one regression note explaining what should not change again.

Before moving on

  • You can state the chapter role: teaches data shape as an engineering choice.
  • You can point to the main boundary: single value, grouped values, and aggregate result.
  • You can connect the invalid case to the problem statement: Readers often see containers as names only, not as data-shape decisions embedded in a complete flow.
  • You can perform the exercise: Replace the single summary form with two grouped buckets and explain why the new shape is justified.