Goal
Turn covariance and contravariance from vocabulary into reflexes by making the type
checker referee every claim: break the unsound cases at runtime, then rebuild them so
the same mistake fails to compile. Along the way, feel the two SOLID ties first-hand —
Liskov substitution as the reason for the variance rules, interface segregation as
the price of declaration-site out/in.
Subject: full brief & instructions
Practices
- Covariance & contravariance — the whole exercise: producers covariant, consumers contravariant, read-write invariant.
- Clean code — LSP and ISP stop being slide material; each part is one of them enforced by a compiler.
- Testing strategies — property tests expose the LSP break; contract tests make substitutability executable.
- Deep dive Java — wildcards, PECS, and the array legacy.
- Deep dive TypeScript — function-type variance and the method-bivariance loophole.
Milestones
- Break arrays (Java). Write the classic
Animal[] a = new Cat[1]; a[0] = new Dog();and watchArrayStoreExceptionat runtime; rewrite withList<Cat>and watch the same mistake refuse to compile. One commit, two files, the runtime→compile-time move stated in a sentence. - Rectangle/Square under property tests. Implement the naive
Square extends Rectangle, write the property (setWidth(w)⇒area == w × height) for any rectangle, watch Square fail it, then fix by redesign — immutable values or separate types — until the property holds for every member of the hierarchy. - PECS drill (Java). Implement
copy(src, dst),max(collection, comparator), andaddAll(target, elements…)with wildcards against a prepared harness of should-compile and must-not-compile call sites. - The ISP→variance kata (Java → Kotlin). Take a fat
MultiFunctionDevice(print + scan + fax), segregate it into role interfaces, port to Kotlin, and annotate producersout/ consumersin. Then try to annotate the original fat interface and keep the compiler's refusal as the artifact: the segregation is what made variance legal. - strictFunctionTypes koans (TypeScript). A single file of function-assignability assertions; flip the flag, explain every new error in a comment, and document the method-bivariance loophole with one case that still slips through.
- LSP as contract tests. Write one abstract contract-test class for a small interface (a repository port works well), run it against every implementation including a deliberately broken one — the broken one failing the contract is the point.
Stretch goals
- Port part 4 to Scala (
+T/-T) or C# (out/in) and compare the dialects. - Work through the variance-flavored puzzles in type-challenges after part 5.
- Archaeology: find one real bivariance-era bug report in the TypeScript issue tracker
and trace why
strictFunctionTypescouldn't fix methods without breaking the DOM types.
Related
- Variance workout — subject — the self-contained work statement: rules, code seeds, constraints, acceptance checks.
- Covariance & contravariance —
the concept note whose
# Practicecites this project. - Clean code, Testing strategies — the principles and the test disciplines drilled throughout.
- Birthday Greetings — hexagonal extraction — its ports make a natural target for part 6's contract tests.