Check Your Understanding
Twelve questions on the Quickstart through the Capstone, each answer proved by the build.
These questions cover the pages from the Quickstart to the Capstone. They start with recall and end with writing code of your own, and they do not follow page order, on purpose. Answer each question before you open its answer, in your head or on paper. Each answer ends with a link to the section that teaches it, and Where to go next turns your score into a plan.
What does OrderFocus.lines() focus, the list or each line? What does OrderFocus.lines().count(order) return for an order of a lamp and four bulbs?
Answer and why
Answer and why
Each line, and 2. A List field's generated method has already stepped into the elements, so the path is a TraversalPath<Order, LineItem>. There are two lines; the four bulbs are one line with a quantity of 4.
Where this lives: Find your field.
Jackson's JsonNode is not your class, so you cannot put @GeneratePrisms on it. How do you get prisms for its object, array and text nodes?
Answer and why
Answer and why
Declare an OpticsSpec interface for JsonNode, annotated @ImportOptics. The processor reads the spec and generates JsonNodeOptics, with one prism per method, which compose with the rest of your optics:
@ImportOptics
public interface JsonNodeOpticsSpec extends OpticsSpec<JsonNode> {
@InstanceOf(ObjectNode.class)
Prism<JsonNode, ObjectNode> object();
@InstanceOf(ArrayNode.class)
Prism<JsonNode, ArrayNode> array();
@InstanceOf(StringNode.class)
Prism<JsonNode, StringNode> text();
@InstanceOf(NumericNode.class)
Prism<JsonNode, NumericNode> numeric();
@InstanceOf(BooleanNode.class)
Prism<JsonNode, BooleanNode> bool();
}
Where this lives: Annotating types you don't own.
A colleague tidies a line's SKU and parses a new price in one update. Would you approve this?
Update<LineItem> tidy =
Edits.combine(
Edit.modify(LineItemFocus.sku(), String::strip),
Edit.parseIfPresent(
LineItemFocus.price(), "40.00", raw -> Validated.validNel(new BigDecimal(raw))));
Answer and why
Answer and why
No: it does not compile, with no suitable method found for combine. parseIfPresent makes a FallibleEdit, and Edits.combine takes only edits that cannot fail, so a failure can never be dropped silently. Put both edits in Edits.accumulate instead.
Where this lives: How the split stays compile-time safe.
A catalogue keeps its prices in a Map:
@GenerateLenses
@GenerateFocus
record Catalogue(String name, Map<String, BigDecimal> prices) {}
What does CatalogueFocus.prices() return, and how do you read the price of LAMP?
Answer and why
Answer and why
A FocusPath to the whole map; .atKey("LAMP") gives an AffinePath to one price. A Map field is not stepped into by default, so its path still focuses the map, and .atKey picks one value, which may be absent.
Where this lives: Access by index.
The Capstone's accept checks every price with modifyAllValidated, then discounts the bulk lines inside map. For an order with a negative price, does the discount run?
Answer and why
Answer and why
No. map on a Validated changes only a Valid, and an Invalid passes through untouched, so the result is Invalid with every bad price, and nothing is discounted.
Where this lives: Take an order in.
Two components may hold null: @Nullable String couponCode, with JSpecify's annotation, and String legacyNote, with none. Which needs .nullable() after its generated method?
Answer and why
Answer and why
Only legacyNote. A recognised @Nullable makes the generated method return an AffinePath already. An unannotated reference gives a FocusPath, and .nullable() reads a null as absent.
Where this lives: .nullable(): read a null as absent.
A PATCH of an order line sends the SKU lamp 2 and the price forty, and both fail their parsers. The edits are written SKU first, then quantity, then price. With Edits.accumulate, how many errors come back, and in what order?
Answer and why
Answer and why
Two, in edit order: the SKU's, then the price's, each located by its path. accumulate checks every edit on its own before it writes anything:
Validated<NonEmptyList<FieldError>, LineItem> patched =
Edits.accumulate(
parseIfPresent(SKU, badPatch.sku(), Sku::parse),
modifyIfPresent(QUANTITY, badPatch.qtyDelta(), (delta, qty) -> qty + delta),
parseIfPresent(PRICE, badPatch.price(), Price::parse))
.apply(line);
// Invalid(NonEmptyList[sku: not a SKU, price: not a price])
// <- or Valid(line) with only the present fields changed
Where this lives: Validated PATCH.
What is the type of OrderTraversals.lines().andThen(LineItemLenses.price())?
Answer and why
Answer and why
Traversal<Order, BigDecimal>. Many followed by exactly one is still many:
Traversal<Order, BigDecimal> prices = OrderTraversals.lines().andThen(LineItemLenses.price());
Where this lives: Composing optics with andThen.
This record lowercases what it is given:
// An email address whose constructor lowercases what it is given
record NormalisedEmail(String value) {
NormalisedEmail {
value = value.toLowerCase(Locale.ROOT);
}
}
Is a lens to its value lawful? What does setting ADA@EXAMPLE.COM and reading it back give?
Answer and why
Answer and why
No: reading back gives ada@example.com, not what was set. A lens promises that you read back what you set, and the constructor changes the value on the way in, so hkj-test's LensLaws reports the broken law:
Lens<NormalisedEmail, String> value =
Lens.of(NormalisedEmail::value, (_, newValue) -> new NormalisedEmail(newValue));
Where this lives: What a lens is, and Use Manual Lens Creation When on checking a hand-written lens.
Write one expression that sets the quantity of every line of an order to 1.
Answer and why
Answer and why
A traversal into the lines, then the quantity, then setAll:
Order oneOfEach = OrderFocus.lines().via(LineItemFocus.quantity()).setAll(1, order);
Where this lives: TraversalPath: Zero or More Elements.
Write a method that accepts an order only if every line has at least one item, and otherwise reports every line that does not.
Answer and why
Answer and why
A check that returns its verdict, run through the quantities with modifyAllValidated:
static Validated<String, Integer> checkQuantity(Integer quantity) {
return quantity >= 1 ? Validated.valid(quantity) : Validated.invalid("No items: " + quantity);
}
static Validated<List<String>, Order> acceptQuantities(Order order) {
return OpticOps.modifyAllValidated(
order,
OrderFocus.lines().via(LineItemFocus.quantity()).toTraversal(),
SelfCheckBook::checkQuantity);
}
Quantities of 0, 4 and -1 give Invalid(["No items: 0", "No items: -1"]).
Where this lives: Every element, every error.
Write an update that upper-cases the reason of a Returned consignment and leaves a consignment in any other state as it is.
Answer and why
Answer and why
A path through the state's returned() prism, then modify, which changes only the variant the prism matches:
Consignment tidied =
ConsignmentFocus.state()
.via(ConsignmentStatePrisms.returned())
.modify(
returned ->
new ConsignmentState.Returned(returned.reason().toUpperCase(Locale.ROOT)),
consignment);
Where this lives: One variant of a sealed type.
The code on this page is SelfCheckBook.java and SelfCheckBookTest.java: the page includes the first, and the test asserts every result an answer predicts. The answers drawn from the earlier pages are proved by those pages' own tests, and the build checks the refused snippet against the compiler's own words. Open them after the twelve, since they hold the answers.
Where to go next
A question counts when every part of your answer was right before you opened it, and code you wrote counts when it compiles to the same result as the answer's. Whatever your score, open the Where this lives link of any answer you missed.
| Your score | Next step |
|---|---|
| 10 to 12, with Checkpoints 11 and 12 among them | You can ship. Read the rest of the chapter as a task calls for it: The Optic Types for each optic in depth, and Look It Up for an answer to a specific question |
| Any other score from 6 to 11 | Reread the sections you missed, try those questions again, then carry on as for 10 to 12 |
| 5 or fewer | Go back to the Quickstart and read each page through to the Capstone, working the Optics Tutorial Track alongside, then take the questions again |
Previous: Capstone: An Order Desk Next: The Optic Types