07-control

Level: beginner · Reading time: 20 min · Prerequisite: book/chapters/06-procedures.html · Track: essential · Maturity: reviewed · Last review: 2026-05-09

TL;DR (5 lines)

  • Control flow is about path ownership.
  • A single scenario teaches branches better than isolated snippets.
  • Each branch should change an observable outcome or invariant.
  • Invalid control examples should isolate structural mistakes.
  • Read the path first, the tokens second.

Frequent mistakes

  • Teaching `if`, `for`, and `match` as unrelated pages with no shared scenario.
  • Adding branches that do not change any observable outcome.
  • Using invalid examples that break types instead of control shape.

Prerequisites: book/chapters/06-procedures.html. See also: book/chapters/06-procedures.html, book/chapters/27-grammar.html, book/chapters/31-build-errors.html.

Concrete Problem

Control-flow chapters become repetitive when they explain keywords separately without one scenario showing how branches cooperate.

Red Thread (Single Project)

One scoring flow uses guards, loops, and branching to keep the path explicit from input to output.

For what

This chapter helps the reader decide when to branch, when to loop, and when to stop.

Work in this chapter

You will inspect one controlled flow, identify the branch points, then compare it to a malformed control surface.

Coherent example

space demo/control

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

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/control

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

export *

Guarded control path

This example makes each branch protect a visible outcome.

space examples/control/guards

proc classify(value: int) -> int {
  if value < 0 { give 10 }
  if value == 0 { give 0 }
  give 1
}

proc main(args: list[string]) -> int {
  give classify(5)
}

export *

Loop with explicit accumulator

This flow keeps repeated work and final state visible.

space examples/control/loop

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

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

export *

Immediate work for this chapter

  • Show the normal path, guard path, and fallback path in one flow.
  • Use branches and loops only when they change a visible result.
  • Keep each control construct tied to a named execution path.
  • 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: 07-control.html

Dedicated problem

This chapter must teach execution paths. The reader should follow guard paths, loop paths, and fallback paths without guessing hidden state.

Specific complete examples

Guarded status flow

Guards appear before the nominal result and return explicit status codes.

space examples/control/guarded_status

proc validate(size: int) -> int {
  if size < 0 { give 11 }
  if size == 0 { give 12 }
  give 0
}

proc main(args: list[string]) -> int {
  give validate(4)
}

export *

Loop accumulator

The accumulator is visible from initialization to final result.

space examples/control/loop_accumulator

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

proc main(args: list[string]) -> int {
  give sum_positive([1, -1, 3])
}

export *

Fallback classification

The fallback path is an intentional policy, not an accidental default.

space examples/control/fallback

proc classify(code: int) -> int {
  if code == 0 { give 0 }
  if code == 1 { give 10 }
  give 99
}

proc main(args: list[string]) -> int {
  give classify(2)
}

export *

Risks and diagnostics

RiskDiagnostic signalAction
Hidden pathA branch changes control flow without visible effect.Return or update a named value in every meaningful branch.
Loop driftThe accumulator is hard to audit.Initialize, update, and return the accumulator clearly.
Accidental fallbackThe final path is a leftover default.Name the fallback as policy in code and prose.

Review checklist

  • Guard path appears before nominal path.
  • Loop state is named and local.
  • Fallback path has a clear status value.
  • Invalid control examples break control shape, not unrelated typing.

Production use

  • Use guard-first control in validation-heavy boundaries.
  • Use loops only when repeated work is the clearest shape.
  • Add regression cases for nominal, guard, and fallback paths.

What to avoid

  • Do not add branches that produce the same observable result.
  • Do not hide fallback policy in an unexplained final `give`.
  • Do not combine loop mutation and broad side effects in the teaching example.

Global explanation

Control flow must be taught as a route through the program, not as isolated keywords. The reader should see where the path changes, why it changes, and how the result remains understandable.

Invalid case

proc bad_control(x: int) -> int {
  if x { give 0 }
  give x
}

This invalid case is intentionally small. It exists to isolate the contract failure that the chapter is trying to teach.

Common pitfalls

  • Teaching `if`, `for`, and `match` as unrelated pages with no shared scenario.
  • Adding branches that do not change any observable outcome.
  • Using invalid examples that break types instead of control shape.

Short exercise

Add one fallback branch to the example and explain what new path it creates.

Summary in 5 points

  1. Control flow is about path ownership.
  2. A single scenario teaches branches better than isolated snippets.
  3. Each branch should change an observable outcome or invariant.
  4. Invalid control examples should isolate structural mistakes.
  5. Read the path first, the tokens second.

See also

Next best action

Extend the coherent example by one small, justified step and keep the same contract visible from input to output.

Chapter deep dive

07-control makes program paths readable and testable. The chapter is written for a reader tracing why execution moved from one branch to another.

The practical boundary is: nominal path, guard path, and fallback path. Keep that boundary in view while reading the example, the invalid case, and the exercise.

Role in the learning path

Control-flow chapters become repetitive when they explain keywords separately without one scenario showing how branches cooperate.

One scoring flow uses guards, loops, and branching to keep the path explicit from input to output.

This chapter helps the reader decide when to branch, when to loop, and when to stop.

Profile-specific deep dive

Guard path

  • Guards should appear before the nominal path they protect.
  • Each guard should return or continue with an observable reason.
  • A guard that changes no result is probably noise.

Loop path

  • Loops need a named accumulator or clear side effect.
  • Continue and break should make the path simpler, not harder to follow.
  • The final result should be traceable without simulating hidden state.

Fallback path

  • Match or branch fallback should be explicit when the input domain is not exhausted.
  • The fallback value should encode policy, not an accidental default.
  • Tests should cover at least one nominal path and one fallback path.

Reading the valid example

  1. space demo/control: names the ownership boundary before any behavior appears.
  2. proc sum_positive(values: list[int]) -> int {: states the callable contract: inputs first, result shape last.
  3. let acc: int = 0: keeps an intermediate decision visible for review.
  4. for value in values {: supports the chapter contract without adding hidden behavior.
  5. if value < 0 { continue }: guards a failure or edge case before the nominal result.
  6. set acc = acc + value: supports the chapter contract without adding hidden behavior.
  7. }: supports the chapter contract without adding hidden behavior.
  8. give acc: ends the local path with an explicit result.
  9. }: supports the chapter contract without adding hidden behavior.
  10. export *: supports the chapter contract without adding hidden behavior.

Lesson from the invalid example

  1. proc bad_control(x: int) -> int {: this line helps isolate the failure because it states the callable contract: inputs first, result shape last.
  2. if x { give 0 }: this line helps isolate the failure because it guards a failure or edge case before the nominal result.
  3. give x: 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: Control flow is about path ownership.
  • Keep the smallest example executable: A single scenario teaches branches better than isolated snippets.
  • Make the invalid path explain one failure only: Each branch should change an observable outcome or invariant.
  • Prefer a visible contract over an implied convention: Invalid control examples should isolate structural mistakes.
  • Leave a review anchor that another maintainer can verify: Read the path first, the tokens second.

Context-specific review criteria

  • The page makes the nominal path, guard path, and fallback path boundary visible before the first code block.
  • The intended reader, a reader tracing why execution moved from one branch to another, 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 nominal path, guard path, and fallback path.
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 nominal path, guard path, and fallback path 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 tracing why execution moved from one branch to another, 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

  • Teaching `if`, `for`, and `match` as unrelated pages with no shared scenario.
  • Adding branches that do not change any observable outcome.
  • Using invalid examples that break types instead of control shape.

Practice scenario

Start from the coherent example in 07-control. 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: makes program paths readable and testable.
  • You can point to the main boundary: nominal path, guard path, and fallback path.
  • You can connect the invalid case to the problem statement: Control-flow chapters become repetitive when they explain keywords separately without one scenario showing how branches cooperate.
  • You can perform the exercise: Add one fallback branch to the example and explain what new path it creates.