Add demo script for printSalesForecasts (Split Loop with Lambda)

Demo on the messier sibling of the exercise method: three unwanted
dependencies in one loop (Scorer incl. static field write, System.out,
clock — clock stripped in 2-min prep). Same six mechanics with SVGs in
the storyboard style, ends green via overload + pure-lambda approval
test. Pointer added to the learning hour's demo section.
This commit is contained in:
Your Name
2026-09-28 07:42:33 +00:00
parent 231f6aa72f
commit 558ad54516
8 changed files with 536 additions and 0 deletions
@@ -84,6 +84,9 @@ If there is time, show one slide: the slice graphic from the
## Demo - The weather slice (10 min) ## Demo - The weather slice (10 min)
Full script for the *complex* variant of this demo — on `DailyForecasts.printSalesForecasts()`,
with SVGs and narration beats — is in [`storyboard/daily-forecasts-demo.md`](storyboard/daily-forecasts-demo.md).
Do the six mechanics on a whiteboard-projection slice of Do the six mechanics on a whiteboard-projection slice of
`LongRangeForecast` — only the `DATES` loop: `LongRangeForecast` — only the `DATES` loop:
@@ -0,0 +1,241 @@
# Demo script — printSalesForecasts
This is what I demo in the Split Loop with Lambda learning hour. I demo on
`DailyForecasts.printSalesForecasts()` — the messier sibling of the method the pairs
will refactor in the exercise. Same pattern, but this loop has three unwanted
dependencies: `Scorer` (with a static field for extra credit), `System.out`, and the
clock.
I remove the clock before we start. The rest stays in. Ten minutes, six steps.
## Before we start (2 min)
Strip the two header lines:
```java
// var formatter = DateTimeFormatter.ISO_LOCAL_DATE.withZone(ZoneId.from(ZoneOffset.UTC));
// System.out.println("Forecast at: " + formatter.format(Instant.now()));
```
I say this out loud while I delete them: *the clock is a third dependency, and we do
one split at a time.*
Then add the test from step 1 and run it once, so the flakiness is a memory before I
start typing.
---
## Step 0 — count the concerns
![](svg/d0-start.svg)
Mutation and value read, a conditional, IO — in twelve lines. I ask: *what does this
loop need from the outside?* Answer: *given a name, what is the sales score?* That
question is the lambda we are about to extract. The rest — printing, the clock — is
not the loop's business.
---
## Step 1 — an approval test: red & flaky
![](svg/d1-test.svg)
```java
@Test
void salesForecasts() {
var out = new ByteArrayOutputStream();
var originalOut = System.out;
System.setOut(new PrintStream(out));
try {
new DailyForecasts().printSalesForecasts();
} finally {
System.setOut(originalOut);
}
Approvals.verify(out.toString());
}
```
Run it. Red, and every run disagrees with the last. Why: `Scorer` re-rolls `flavour`
with a `Random`. Same situation as the exercise method — that is the point of
demoing this one.
---
## Step 2 — extract the dependency-using code into a lambda
![](svg/d2-extract-lambda.svg)
**After:**
```java
public void printSalesForecasts() {
var names = List.of("Steve", "Julie", "Francis");
Function<String, Integer> nameToSalesScore = name -> { // ← NEW: the lambda...
if (Objects.equals(name, "Steve")) {
Scorer.flavour = IceCream.Strawberry; // ← ...holds BOTH scorer touches
} else {
scorer.updateSelection();
}
return scorer.getSalesForecast();
};
for (String name : names) {
System.out.println(name + " score: " + nameToSalesScore.apply(name)); // ← invocation replaced getSalesForecast
}
}
```
I say: *watch what goes inside — all of it. The static write too. If you leave the
`Scorer.flavour = ...` behind in the loop, the extraction later drags the whole class
in with it.* (The exercise pairs will meet this as the drift; here I show it before
it can happen.)
**Checkpoint:** compiles; behaviour unchanged; test still red & flaky. Also true and
worth saying: *this lambda is not pure. The unwanted dependency lives inside it —
that is exactly what we want. It is an adapter.*
---
## Step 3 — slide the lambda definition to the top of the method
![](svg/d3-slide.svg)
**After:**
```java
public void printSalesForecasts() {
Function<String, Integer> nameToSalesScore = name -> { // ← SLID to the top...
if (Objects.equals(name, "Steve")) {
Scorer.flavour = IceCream.Strawberry;
} else {
scorer.updateSelection();
}
return scorer.getSalesForecast();
};
var names = List.of("Steve", "Julie", "Francis");
for (String name : names) {
System.out.println(name + " score: " + nameToSalesScore.apply(name)); // ← ...invocation stays here
}
}
```
Slide, don't move. Watch the invocation — it stays exactly where the code used to
be.
**Checkpoint:** compiles; test still red & flaky (unchanged behaviour).
---
## Step 4 — extract the loop; the lambda becomes a parameter
![](svg/d4-extract.svg)
Select from the loop through its closing brace, Extract Method.
**After:**
```java
public void printSalesForecasts() {
Function<String, Integer> nameToSalesScore = name -> {
if (Objects.equals(name, "Steve")) {
Scorer.flavour = IceCream.Strawberry;
} else {
scorer.updateSelection();
}
return scorer.getSalesForecast();
};
var names = List.of("Steve", "Julie", "Francis");
var scores = collectSalesScores(names, nameToSalesScore); // ← EXTRACTED
for (var entry : scores.entrySet()) {
System.out.println(entry.getKey() + " score: " + entry.getValue());
}
}
private static LinkedHashMap<String, Integer> collectSalesScores( // ← NEW — and already static
List<String> names, Function<String, Integer> nameToSalesScore) {
var scores = new LinkedHashMap<String, Integer>();
for (String name : names) {
scores.put(name, nameToSalesScore.apply(name));
}
return scores;
}
```
(`LinkedHashMap` because the demo prints names in order; a plain `HashMap` would
shuffle them and confuse everyone. I say that out loud too.)
**Checkpoint — the drift test:**
- `grep -n scorer src/main/java/codingdojo/DailyForecasts.java` — nothing in the new
method.
- Try to make it `static` (it already is). If the static write had stayed in the
loop, this would not compile.
- `System.out` is still in `printSalesForecasts` — that is fine. One split at a
time; the printing loop is next time's material.
---
## Step 5 — the overload, and the test goes green
![](svg/d5-overload-test.svg)
Change Signature on the big method: the lambda becomes a parameter. A no-arg overload
bakes in the real lambda, so callers are unchanged.
```java
public void printSalesForecasts() { // ← no-arg overload: callers unchanged
printSalesForecasts(name -> {
if (Objects.equals(name, "Steve")) {
Scorer.flavour = IceCream.Strawberry;
} else {
scorer.updateSelection();
}
return scorer.getSalesForecast();
});
}
public void printSalesForecasts( // ← the seam: λ is a parameter
Function<String, Integer> nameToSalesScore) {
var names = List.of("Steve", "Julie", "Francis");
var scores = collectSalesScores(names, nameToSalesScore);
for (var entry : scores.entrySet()) {
System.out.println(entry.getKey() + " score: " + entry.getValue());
}
}
```
The test goes through the seam with a pure lambda:
```java
@Test
void salesForecasts() {
var out = new ByteArrayOutputStream();
var originalOut = System.out;
System.setOut(new PrintStream(out));
try {
new DailyForecasts().printSalesForecasts(
name -> Objects.equals(name, "Steve") ? 9 : 11);
} finally {
System.setOut(originalOut);
}
Approvals.verify(out.toString());
}
```
Approved output:
```
Steve score: 9
Julie score: 11
Francis score: 11
```
**Checkpoint:** green — deterministically. `Scorer` is never touched by the test.
---
## End of demo
Same three moves as the exercise: extract, slide, extract. `Scorer` is out of the
loop, and the lambda made it visible that the whole question is just "given a name,
what is the sales score?" `System.out` and the clock are still in
`printSalesForecasts` — that is the next hour, not this one.
@@ -0,0 +1,44 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 760 460" font-family="Helvetica, Arial, sans-serif">
<defs>
<marker id="arrR" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M0,0 L10,5 L0,10 z" fill="#dc2626"/>
</marker>
<marker id="arrO" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M0,0 L10,5 L0,10 z" fill="#d97706"/>
</marker>
</defs>
<rect width="760" height="460" fill="#ffffff"/>
<text x="24" y="34" font-size="20" font-weight="bold" fill="#0f172a">Demo — printSalesForecasts: one loop, three unwanted dependencies</text>
<text x="24" y="56" font-size="13" fill="#475569">The clock is already out (prep). Count the rest: a static side-channel write, a random re-roll, a value read, and IO.</text>
<!-- method -->
<rect x="60" y="74" width="380" height="250" rx="10" fill="#f8fafc" stroke="#64748b" stroke-width="1.5"/>
<text x="76" y="98" font-size="14" font-weight="bold" fill="#0f172a">printSalesForecasts()</text>
<!-- loop -->
<rect x="80" y="112" width="340" height="196" rx="8" fill="#eef2ff" stroke="#818cf8"/>
<text x="94" y="136" font-size="11" font-family="Menlo, Consolas, monospace" fill="#1e1b4b">for (String name : names)</text>
<text x="94" y="158" font-size="11" font-family="Menlo, Consolas, monospace" fill="#1e1b4b">if (name.equals("Steve"))</text>
<text x="94" y="180" font-size="11" font-family="Menlo, Consolas, monospace" font-weight="bold" fill="#dc2626"> Scorer.flavour = Strawberry;</text>
<text x="94" y="202" font-size="11" font-family="Menlo, Consolas, monospace" fill="#1e1b4b">else</text>
<text x="94" y="224" font-size="11" font-family="Menlo, Consolas, monospace" font-weight="bold" fill="#dc2626"> scorer.updateSelection();</text>
<text x="94" y="246" font-size="11" font-family="Menlo, Consolas, monospace" font-weight="bold" fill="#dc2626">score = scorer.getSalesForecast();</text>
<text x="94" y="274" font-size="11" font-family="Menlo, Consolas, monospace" font-weight="bold" fill="#d97706">System.out.println(name + " score: " + score);</text>
<text x="94" y="298" font-size="10" fill="#64748b">the weather/sales loop</text>
<!-- Scorer -->
<rect x="580" y="110" width="150" height="90" rx="8" fill="#fef2f2" stroke="#dc2626" stroke-width="1.5"/>
<text x="655" y="140" font-size="14" font-weight="bold" fill="#dc2626" text-anchor="middle">Scorer</text>
<text x="655" y="160" font-size="11" fill="#7f1d1d" text-anchor="middle">Random + a static</text>
<text x="655" y="176" font-size="11" fill="#7f1d1d" text-anchor="middle">field (flavour)</text>
<line x1="424" y1="180" x2="572" y2="140" stroke="#dc2626" stroke-width="1.5" stroke-dasharray="5 4" marker-end="url(#arrR)"/>
<line x1="424" y1="224" x2="572" y2="160" stroke="#dc2626" stroke-width="1.5" stroke-dasharray="5 4" marker-end="url(#arrR)"/>
<line x1="424" y1="246" x2="572" y2="182" stroke="#dc2626" stroke-width="1.5" stroke-dasharray="5 4" marker-end="url(#arrR)"/>
<!-- System.out -->
<rect x="580" y="250" width="150" height="60" rx="8" fill="#fffbeb" stroke="#d97706" stroke-width="1.5"/>
<text x="655" y="274" font-size="12" font-weight="bold" fill="#d97706" text-anchor="middle">System.out</text>
<text x="655" y="292" font-size="10" fill="#92400e" text-anchor="middle">IO — next time's split</text>
<line x1="424" y1="274" x2="572" y2="282" stroke="#d97706" stroke-width="1.5" stroke-dasharray="5 4" marker-end="url(#arrO)"/>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

@@ -0,0 +1,37 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 760 460" font-family="Helvetica, Arial, sans-serif">
<defs>
<marker id="arrR" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M0,0 L10,5 L0,10 z" fill="#dc2626"/>
</marker>
<marker id="arrG" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M0,0 L10,5 L0,10 z" fill="#475569"/>
</marker>
</defs>
<rect width="760" height="460" fill="#ffffff"/>
<text x="24" y="34" font-size="20" font-weight="bold" fill="#0f172a">Step 1 — an approval test: red &amp; flaky</text>
<text x="24" y="56" font-size="13" fill="#475569">Capture System.out, Approvals.verify it. Every run disagrees — Scorer re-rolls with a Random.</text>
<!-- test -->
<rect x="50" y="140" width="240" height="140" rx="10" fill="#ffffff" stroke="#94a3b8" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="66" y="166" font-size="13" font-weight="bold" fill="#334155">DailyForecastsTest</text>
<text x="66" y="192" font-size="11" font-family="Menlo, Consolas, monospace" fill="#334155">capture System.out</text>
<text x="66" y="212" font-size="11" font-family="Menlo, Consolas, monospace" fill="#334155">Approvals.verify(...)</text>
<text x="66" y="248" font-size="12" font-weight="bold" fill="#dc2626">red &amp; flaky ✗</text>
<!-- method -->
<rect x="380" y="100" width="330" height="180" rx="10" fill="#f8fafc" stroke="#64748b" stroke-width="1.5"/>
<text x="396" y="124" font-size="13" font-weight="bold" fill="#0f172a">printSalesForecasts()</text>
<rect x="396" y="138" width="298" height="126" rx="8" fill="#eef2ff" stroke="#818cf8"/>
<text x="410" y="162" font-size="11" font-family="Menlo, Consolas, monospace" fill="#1e1b4b">for (String name : names)</text>
<text x="410" y="186" font-size="11" font-family="Menlo, Consolas, monospace" font-weight="bold" fill="#dc2626">Scorer.flavour = ... / updateSelection()</text>
<text x="410" y="210" font-size="11" font-family="Menlo, Consolas, monospace" font-weight="bold" fill="#dc2626">score = scorer.getSalesForecast();</text>
<text x="410" y="238" font-size="11" font-family="Menlo, Consolas, monospace" fill="#d97706">System.out.println(...)</text>
<line x1="294" y1="210" x2="372" y2="190" stroke="#475569" stroke-width="1.5" marker-end="url(#arrG)"/>
<!-- Scorer -->
<rect x="560" y="330" width="170" height="70" rx="8" fill="#fef2f2" stroke="#dc2626" stroke-width="1.5"/>
<text x="645" y="358" font-size="12" font-weight="bold" fill="#dc2626" text-anchor="middle">Scorer</text>
<text x="645" y="378" font-size="10" fill="#7f1d1d" text-anchor="middle">Random → different every run</text>
<line x1="645" y1="328" x2="645" y2="286" stroke="#dc2626" stroke-width="1.5" stroke-dasharray="5 4" marker-end="url(#arrR)"/>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

@@ -0,0 +1,40 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 760 460" font-family="Helvetica, Arial, sans-serif">
<defs>
<marker id="arrR" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M0,0 L10,5 L0,10 z" fill="#dc2626"/>
</marker>
</defs>
<rect width="760" height="460" fill="#ffffff"/>
<text x="24" y="34" font-size="20" font-weight="bold" fill="#0f172a">Step 2 — extract the dependency-using code into a lambda</text>
<text x="24" y="56" font-size="13" fill="#dc2626" font-weight="bold">ALL of it goes inside — the static write too. The lambda is the adapter; it is not supposed to be pure.</text>
<!-- method -->
<rect x="60" y="74" width="380" height="356" rx="10" fill="#f8fafc" stroke="#64748b" stroke-width="1.5"/>
<text x="76" y="98" font-size="14" font-weight="bold" fill="#0f172a">printSalesForecasts()</text>
<!-- loop -->
<rect x="80" y="184" width="340" height="152" rx="8" fill="#eef2ff" stroke="#818cf8"/>
<text x="94" y="204" font-size="11" font-family="Menlo, Consolas, monospace" fill="#1e1b4b">for (String name : names)</text>
<!-- lambda -->
<rect x="94" y="214" width="312" height="90" rx="8" fill="#faf5ff" stroke="#9333ea" stroke-width="1.5"/>
<text x="106" y="236" font-size="12" font-weight="bold" fill="#9333ea">λ nameToSalesScore</text>
<text x="106" y="258" font-size="11" font-family="Menlo, Consolas, monospace" fill="#dc2626">if (Steve) Scorer.flavour = Strawberry;</text>
<text x="106" y="276" font-size="11" font-family="Menlo, Consolas, monospace" fill="#dc2626">else scorer.updateSelection();</text>
<text x="106" y="294" font-size="11" font-family="Menlo, Consolas, monospace" fill="#dc2626">return scorer.getSalesForecast();</text>
<circle cx="90" cy="326" r="4" fill="#9333ea"/>
<text x="102" y="330" font-size="11" font-family="Menlo, Consolas, monospace" font-weight="bold" fill="#6b21a8">println(name + " score: " + λ.apply(name));</text>
<text x="94" y="366" font-size="10" fill="#d97706" font-weight="bold">System.out.println stays in the loop — next time's split</text>
<text x="94" y="414" font-size="10" fill="#64748b">names list lives above the loop, unchanged</text>
<!-- Scorer -->
<rect x="580" y="200" width="150" height="100" rx="8" fill="#fef2f2" stroke="#dc2626" stroke-width="1.5"/>
<text x="655" y="230" font-size="14" font-weight="bold" fill="#dc2626" text-anchor="middle">Scorer</text>
<text x="655" y="250" font-size="11" fill="#7f1d1d" text-anchor="middle">static field + Random</text>
<text x="655" y="268" font-size="11" fill="#7f1d1d" text-anchor="middle">both touches in the λ now</text>
<line x1="410" y1="250" x2="572" y2="234" stroke="#dc2626" stroke-width="1.5" stroke-dasharray="5 4" marker-end="url(#arrR)"/>
<line x1="410" y1="288" x2="572" y2="262" stroke="#dc2626" stroke-width="1.5" stroke-dasharray="5 4" marker-end="url(#arrR)"/>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

@@ -0,0 +1,48 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 760 460" font-family="Helvetica, Arial, sans-serif">
<defs>
<marker id="arrR" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M0,0 L10,5 L0,10 z" fill="#dc2626"/>
</marker>
<marker id="arrP" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M0,0 L10,5 L0,10 z" fill="#9333ea"/>
</marker>
</defs>
<rect width="760" height="460" fill="#ffffff"/>
<text x="24" y="34" font-size="20" font-weight="bold" fill="#0f172a">Step 3 — slide the lambda definition to the top of the method</text>
<text x="24" y="56" font-size="13" fill="#9333ea" font-weight="bold">Slide, don't move: the definition slides up, the invocation stays exactly where it was.</text>
<!-- method -->
<rect x="60" y="74" width="380" height="356" rx="10" fill="#f8fafc" stroke="#64748b" stroke-width="1.5"/>
<text x="76" y="98" font-size="14" font-weight="bold" fill="#0f172a">printSalesForecasts()</text>
<!-- lambda at top -->
<rect x="94" y="104" width="312" height="90" rx="8" fill="#faf5ff" stroke="#9333ea" stroke-width="1.5"/>
<text x="106" y="126" font-size="12" font-weight="bold" fill="#9333ea">λ nameToSalesScore</text>
<text x="106" y="148" font-size="11" font-family="Menlo, Consolas, monospace" fill="#dc2626">if (Steve) Scorer.flavour = Strawberry;</text>
<text x="106" y="166" font-size="11" font-family="Menlo, Consolas, monospace" fill="#dc2626">else scorer.updateSelection();</text>
<text x="106" y="184" font-size="11" font-family="Menlo, Consolas, monospace" fill="#dc2626">return scorer.getSalesForecast();</text>
<!-- loop -->
<rect x="80" y="252" width="340" height="110" rx="8" fill="#eef2ff" stroke="#818cf8"/>
<text x="94" y="274" font-size="11" font-family="Menlo, Consolas, monospace" fill="#1e1b4b">for (String name : names)</text>
<!-- ghost -->
<rect x="94" y="286" width="312" height="30" rx="6" fill="none" stroke="#9333ea" stroke-width="1" stroke-dasharray="4 3"/>
<text x="106" y="306" font-size="10" fill="#9333ea" font-style="italic">λ used to be defined here</text>
<circle cx="90" cy="346" r="4" fill="#9333ea"/>
<text x="102" y="350" font-size="11" font-family="Menlo, Consolas, monospace" font-weight="bold" fill="#6b21a8">println(name + " score: " + λ.apply(name));</text>
<!-- slide arrow -->
<line x1="250" y1="284" x2="250" y2="200" stroke="#9333ea" stroke-width="1.5" stroke-dasharray="5 4" marker-end="url(#arrP)"/>
<text x="260" y="244" font-size="12" font-weight="bold" fill="#9333ea">slide</text>
<!-- Scorer -->
<rect x="580" y="200" width="150" height="100" rx="8" fill="#fef2f2" stroke="#dc2626" stroke-width="1.5"/>
<text x="655" y="230" font-size="14" font-weight="bold" fill="#dc2626" text-anchor="middle">Scorer</text>
<text x="655" y="250" font-size="11" fill="#7f1d1d" text-anchor="middle">static field + Random</text>
<text x="655" y="268" font-size="11" fill="#7f1d1d" text-anchor="middle">both touches in the λ now</text>
<line x1="410" y1="140" x2="572" y2="230" stroke="#dc2626" stroke-width="1.5" stroke-dasharray="5 4" marker-end="url(#arrR)"/>
<line x1="410" y1="180" x2="572" y2="264" stroke="#dc2626" stroke-width="1.5" stroke-dasharray="5 4" marker-end="url(#arrR)"/>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

@@ -0,0 +1,69 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 760 460" font-family="Helvetica, Arial, sans-serif">
<defs>
<marker id="arrR" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M0,0 L10,5 L0,10 z" fill="#dc2626"/>
</marker>
<marker id="arrO" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M0,0 L10,5 L0,10 z" fill="#d97706"/>
</marker>
<marker id="arrP" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M0,0 L10,5 L0,10 z" fill="#9333ea"/>
</marker>
</defs>
<rect width="760" height="460" fill="#ffffff"/>
<text x="24" y="34" font-size="20" font-weight="bold" fill="#0f172a">Step 4 — extract the loop; the lambda becomes a parameter</text>
<text x="24" y="56" font-size="13" fill="#475569">collectSalesScores is static and scorer-free. The printing loop stays behind — one split at a time.</text>
<!-- left: composed method -->
<rect x="50" y="74" width="300" height="356" rx="10" fill="#f8fafc" stroke="#64748b" stroke-width="1.5"/>
<text x="66" y="96" font-size="14" font-weight="bold" fill="#0f172a">printSalesForecasts()</text>
<!-- lambda -->
<rect x="64" y="104" width="272" height="68" rx="8" fill="#faf5ff" stroke="#9333ea" stroke-width="1.5"/>
<text x="78" y="126" font-size="12" font-weight="bold" fill="#9333ea">λ nameToSalesScore</text>
<text x="78" y="146" font-size="10" font-family="Menlo, Consolas, monospace" fill="#dc2626">static write + updateSelection +</text>
<text x="78" y="162" font-size="10" font-family="Menlo, Consolas, monospace" fill="#dc2626">getSalesForecast()</text>
<!-- call site -->
<rect x="64" y="188" width="272" height="48" rx="8" fill="#ffffff" stroke="#475569" stroke-width="1.5"/>
<text x="78" y="208" font-size="11" font-family="Menlo, Consolas, monospace" fill="#0f172a">var scores =</text>
<text x="78" y="226" font-size="11" font-family="Menlo, Consolas, monospace" fill="#0f172a">collectSalesScores(names, <tspan fill="#9333ea" font-weight="bold">λ</tspan>)</text>
<!-- print loop -->
<rect x="64" y="252" width="272" height="72" rx="8" fill="#eef2ff" stroke="#818cf8"/>
<text x="78" y="276" font-size="11" font-family="Menlo, Consolas, monospace" font-weight="bold" fill="#d97706">for (entry : scores)</text>
<text x="78" y="296" font-size="11" font-family="Menlo, Consolas, monospace" font-weight="bold" fill="#d97706">System.out.println(...)</text>
<text x="78" y="316" font-size="10" fill="#d97706">IO stays — next time</text>
<!-- purple arrow -->
<line x1="340" y1="212" x2="452" y2="146" stroke="#9333ea" stroke-width="1.5" marker-end="url(#arrP)"/>
<text x="396" y="188" font-size="10" font-weight="bold" fill="#9333ea" text-anchor="middle" transform="rotate(-48 396 188)">λ becomes a parameter</text>
<!-- right: extracted method -->
<rect x="440" y="74" width="290" height="280" rx="10" fill="#f8fafc" stroke="#64748b" stroke-width="1.5"/>
<text x="456" y="96" font-size="13" font-weight="bold" fill="#0f172a">collectSalesScores</text>
<text x="456" y="112" font-size="11" fill="#475569">static — no fields, all inputs are parameters</text>
<rect x="456" y="122" width="258" height="32" rx="16" fill="#faf5ff" stroke="#9333ea" stroke-width="1.5"/>
<text x="585" y="142" font-size="11" font-weight="bold" fill="#9333ea" text-anchor="middle">λ Function&lt;String, Integer&gt; nameToSalesScore</text>
<rect x="456" y="166" width="258" height="110" rx="8" fill="#eef2ff" stroke="#818cf8"/>
<text x="470" y="190" font-size="11" font-family="Menlo, Consolas, monospace" fill="#1e1b4b">for (String name : names)</text>
<text x="470" y="214" font-size="11" font-family="Menlo, Consolas, monospace" fill="#1e1b4b">scores.put(name,</text>
<text x="470" y="234" font-size="11" font-family="Menlo, Consolas, monospace" fill="#1e1b4b"> <tspan fill="#9333ea" font-weight="bold">λ</tspan>.apply(name))</text>
<text x="470" y="260" font-size="10" fill="#64748b">LinkedHashMap — keeps the order</text>
<text x="456" y="308" font-size="11" font-family="Menlo, Consolas, monospace" fill="#0f172a">return scores;</text>
<!-- Scorer, out of reach -->
<rect x="560" y="386" width="160" height="50" rx="8" fill="#fef2f2" stroke="#dc2626" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="640" y="408" font-size="12" font-weight="bold" fill="#dc2626" text-anchor="middle">Scorer</text>
<text x="640" y="426" font-size="10" fill="#7f1d1d" text-anchor="middle">out of reach from in here</text>
<line x1="640" y1="384" x2="640" y2="360" stroke="#dc2626" stroke-width="1.5" stroke-dasharray="5 4" marker-end="url(#arrR)"/>
<text x="650" y="376" font-size="10" font-weight="bold" fill="#dc2626">✗ cannot reach in</text>
<!-- System.out note -->
<text x="64" y="360" font-size="10" font-weight="bold" fill="#d97706">grep scorer → no hits in here</text>
<text x="64" y="378" font-size="10" font-weight="bold" fill="#d97706">…but System.out is still on this side</text>
<line x1="200" y1="348" x2="330" y2="326" stroke="#d97706" stroke-width="1" stroke-dasharray="4 3" marker-end="url(#arrO)"/>
</svg>

After

Width:  |  Height:  |  Size: 5.2 KiB

@@ -0,0 +1,54 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 760 460" font-family="Helvetica, Arial, sans-serif">
<defs>
<marker id="arrR" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M0,0 L10,5 L0,10 z" fill="#dc2626"/>
</marker>
<marker id="arrGr" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M0,0 L10,5 L0,10 z" fill="#16a34a"/>
</marker>
</defs>
<rect width="760" height="460" fill="#ffffff"/>
<text x="24" y="34" font-size="20" font-weight="bold" fill="#0f172a">Step 5 — the overload, and the test goes green</text>
<text x="24" y="56" font-size="13" fill="#16a34a" font-weight="bold">The test passes nameToSalesScore through the seam. Scorer is never touched: deterministic, glanceable.</text>
<!-- test -->
<rect x="50" y="150" width="230" height="140" rx="10" fill="#f0fdf4" stroke="#16a34a" stroke-width="1.5"/>
<text x="66" y="176" font-size="13" font-weight="bold" fill="#14532d">DailyForecastsTest</text>
<text x="66" y="202" font-size="11" font-family="Menlo, Consolas, monospace" fill="#334155">capture System.out</text>
<text x="66" y="224" font-size="11" font-family="Menlo, Consolas, monospace" font-weight="bold" fill="#16a34a">name -&gt; Steve ? 9 : 11</text>
<text x="66" y="248" font-size="11" font-family="Menlo, Consolas, monospace" fill="#14532d">printSalesForecasts(λ)</text>
<text x="66" y="272" font-size="11" font-family="Menlo, Consolas, monospace" fill="#14532d">Approvals.verify(...)</text>
<!-- middle: overload -->
<rect x="340" y="150" width="190" height="180" rx="10" fill="#f8fafc" stroke="#64748b" stroke-width="1.5"/>
<text x="354" y="176" font-size="11" font-weight="bold" fill="#0f172a">printSalesForecasts(<tspan fill="#9333ea">λ</tspan>)</text>
<text x="354" y="196" font-size="10" fill="#475569">no-arg overload bakes</text>
<text x="354" y="210" font-size="10" fill="#475569">in the real λ</text>
<text x="354" y="238" font-size="11" font-family="Menlo, Consolas, monospace" fill="#1e1b4b">collectSalesScores</text>
<text x="354" y="256" font-size="11" font-family="Menlo, Consolas, monospace" fill="#1e1b4b">(names, <tspan fill="#9333ea" font-weight="bold">λ</tspan>)</text>
<text x="354" y="284" font-size="11" font-family="Menlo, Consolas, monospace" fill="#d97706">print loop</text>
<text x="354" y="304" font-size="11" font-family="Menlo, Consolas, monospace" fill="#d97706">(still in here)</text>
<!-- right: extracted method -->
<rect x="570" y="150" width="170" height="180" rx="10" fill="#f8fafc" stroke="#64748b" stroke-width="1.5"/>
<text x="584" y="176" font-size="10" font-weight="bold" fill="#0f172a">collectSalesScores(λ)</text>
<text x="584" y="202" font-size="10" font-family="Menlo, Consolas, monospace" fill="#1e1b4b">for name : names</text>
<text x="584" y="224" font-size="10" font-family="Menlo, Consolas, monospace" fill="#1e1b4b">scores.put(name,</text>
<text x="584" y="242" font-size="10" font-family="Menlo, Consolas, monospace" fill="#1e1b4b"> <tspan fill="#9333ea" font-weight="bold">λ</tspan>.apply(name))</text>
<text x="584" y="270" font-size="10" font-family="Menlo, Consolas, monospace" fill="#1e1b4b">return scores</text>
<text x="584" y="304" font-size="10" fill="#16a34a" font-weight="bold">pure ✓</text>
<!-- green arrows -->
<line x1="284" y1="225" x2="334" y2="225" stroke="#16a34a" stroke-width="1.5" marker-end="url(#arrGr)"/>
<text x="284" y="214" font-size="10" fill="#16a34a">λ</text>
<line x1="534" y1="225" x2="564" y2="225" stroke="#16a34a" stroke-width="1.5" marker-end="url(#arrGr)"/>
<!-- scorer, never called -->
<rect x="570" y="370" width="170" height="56" rx="8" fill="#f8fafc" stroke="#94a3b8" stroke-width="1.5" stroke-dasharray="4 3" opacity="0.8"/>
<text x="655" y="392" font-size="12" font-weight="bold" fill="#94a3b8" text-anchor="middle">Scorer</text>
<text x="655" y="412" font-size="10" fill="#64748b" text-anchor="middle">never called by the test ✗</text>
<line x1="655" y1="368" x2="655" y2="336" stroke="#dc2626" stroke-width="1.5" stroke-dasharray="5 4" marker-end="url(#arrR)"/>
<text x="665" y="356" font-size="11" font-weight="bold" fill="#dc2626">✗</text>
<text x="50" y="430" font-size="12" fill="#475569">Approved: Steve score: 9 / Julie score: 11 / Francis score: 11 — every run, not just a lucky roll.</text>
</svg>

After

Width:  |  Height:  |  Size: 4.4 KiB