v0.4.5 (22 May 2026)

v0.4.5 on GitHub

A Uniform Instances Facade for Type-Class Lookup

This release introduces Instances, a single static entry point for obtaining any built-in type-class instance, replacing the three inconsistent legacy idioms (a static INSTANCE field, a generic instance() method, or an argument-taking constructor) with one predictable shape discovered by capability through IDE autocomplete. The whole codebase (tests, runnable examples, and the book) is migrated to the one idiom.

  • Obtaining Instances: New org.higherkindedj.hkt.instances package with the Instances facade and the Witnesses typed-token helper. Instances.monad/applicative/functor(token) are total (every canonical instance is at least a Monad); a single token yields all three by Java subtyping. Phantom-typed witnesses (Either, Reader, Context, State) still infer their type parameter from the assignment target, matching EitherMonad.<L>instance() behaviour. The facade is a thin static re-export of the existing accessors (not Spring-wired, not PathRegistry/ServiceLoader-backed) so compile-time safety is preserved and no built-in instance can be missing at runtime (#522)
  • Partial capability lookups: Instances.monadError, monadZero and alternative for canonical instances that implement the richer capability (e.g. Maybe, Optional, Try, Either, List, Stream). The error type E of monadError is inferred from the assignment target; asking for a capability the instance does not have fails fast with a ClassCastException, exactly as calling a non-existent method would
  • Argument-carrying re-exports: Instances.validated(Semigroup), writer(Monoid), eitherT(outer), maybeT(outer), optionalT(outer), readerT(outer), stateT(outer) and writerT(outer, Monoid). The structurally-required dependency is now a compiler-enforced, self-documenting method parameter instead of something discovered by reading a constructor
  • One-idiom migration: The Instances facade is adopted across ~196 test files, 66 runnable examples, and 71 book pages so the documentation and examples teach a single way to obtain an instance. The MonadReader/MonadState MTL capability classes and Traverse/Selective/Foldable remain a separate surface, intentionally out of scope for this facade and tracked separately
  • Reference material: New glossary entry, a Type-Class Instances section in the cheat sheet, and a InstancesFacadeExample runnable example
  • Bifunctor law verification: The reusable Bifunctor law harness (LawTestPattern/TypeClassTestPattern) now verifies the first-map (first(f, fab) == bimap(f, id, fab)) and second-map (second(g, fab) == bimap(id, g, fab)) consistency laws alongside the existing identity and composition laws, so every canonical instance (Either, Tuple, Const, Validated, Writer) is checked against all four laws; explicit named consistency tests added for Either (Left/Right) and Tuple (#461)
  • Collection-path fold family: ListPath and StreamPath gain the monoid-style fold(identity, op) and the Foldable-style foldMap(Monoid, fn), plus foldRight on StreamPath, matching the existing VStreamPath/VStreamContext fold surface so the same reduction reads identically across every sequence-like path and stays inside the path chain. Documented in the cheat sheet and the Foldable chapter, with CollectionPathsExample updated to fold without unwrapping (#462)
  • Effect Path toString() standardisation: A shared PathToString helper gives every Effect Path type one debugging-friendly, greppable toString() convention: the round-parenthesis wrapper form TypeName(inner), a uniform angle-bracketed sentinel vocabulary (<deferred>, <stream>, <empty>, <pending>, <failed>), and bounded rendering for collection-backed paths (ListPath, NonDetPath, WriterPath logs) with an explicit …(+k more) marker so a large backing collection never produces an unbounded log line. IdPath is now null-safe and LazyPath never forces its computation when rendered; all changed path classes hold at 100% line/branch coverage (#530)

Expanded hkj-checker Compile-Time Diagnostics

This release grows the hkj-checker javac plugin from a single Path-type-mismatch check into a catalogue of twelve compile-time checks, adds per-check severity configuration, and consolidates the relevant OpenRewrite recipes into compile-time feedback. Several further candidate checks were investigated and deliberately not shipped (kept as passing characterisation tests that document why) because the targeted error is unreachable on modern javac, already caught by the compiler, or only detectable via a rot-prone heuristic; the strict no-false-positives policy is preserved throughout.

  • Compile-Time Checks: Eleven new checks join path-type-mismatch: effect-composition, transformer-missing-monad, free-switch-exhaustive, discarded-effect, state-t-mapt-arity, error-type-mismatch, kind-value-narrow, witness-arity, via-non-path, map-nests-effect, and migration-nudge. Each is a companion to a real javac error or the sole signal for an otherwise-silent mistake (a discarded lazy effect, a silently-erased error type, a nested effect); the sole-signal heuristics default to a warning
  • Per-check severity: The plugin-argument grammar adds severity:<id>=error|warn alongside the global severity= and disable=<id>, so the warn-default checks can be promoted per project. Unknown ids and unparseable values are ignored so a typo never breaks the build
  • Recipe consolidation: migration-nudge folds the ConvertRawFreeToFreePath and DetectInjectBoilerplate OpenRewrite diagnoses into advisory compile-time nudges; free-switch-exhaustive and witness-arity do the same for the Free-switch and WitnessArity recipes
  • Documentation: tooling/compile_checks.md is now the authoritative checker catalogue (every check, its default severity, and the configuration grammar); the effect/transformers/optics Common Compiler Errors chapters were corrected where they described errors modern javac no longer emits and cross-linked to the catalogue

Hardened hkj-openrewrite Recipes

This release audits and hardens the hkj-openrewrite migration recipes: correctness fixes, broader detection, type-safe matching, new 0.5.0 deprecation recipes, and a near-quadrupled test suite (9 → 34 tests).

  • Arity bounds: AddArityBoundsToTypeParameters now emits TypeArity.Binary for Kind2, Bifunctor and Profunctor (previously always Unary, which generated incorrect bounds), detects witness use across fields, local variables, the class hierarchy, nested generics and wildcard bounds (not just method signatures), and no longer emits malformed output (<Fextends …>); the existing-bound intersection case is also fixed
  • Type-safe detection: ConvertRawFreeToFreePath and DetectInjectBoilerplate use a type-attributed MethodMatcher instead of rendered-string matching (a user type named Free, fully-qualified calls, or static imports no longer mis-fire or get missed); AddHandleErrorCase now also handles switch expressions with whole-word case matching; the three detect-only recipes emit OpenRewrite search-result markers instead of rewriting source with TODO comments
  • 0.5.0 deprecation migration: New MigrateDeprecationsTo0_5_0 recipe group renames StateTKind.narrowK → narrow and KindValidator.narrowWithPattern → narrowHolder

Library and Build Refinements

This release also marks one wildcard-witness escape hatch for removal, makes the Java 25 toolchain self-provisioning, and lands the module-internal foundation for batched optic data access.

  • StateTKind.narrowK deprecation: StateTKind.narrowK accepts a wildcard-witness Kind<?, A>, bypassing the HKT witness type safety enforced everywhere else in the library; it has no callers and the type-safe narrow(Kind) already covers the use case. It is now @Deprecated(forRemoval = true) for removal in 0.5.0, with the MigrateDeprecationsTo0_5_0 OpenRewrite recipe automating the narrowK → narrow rename (#455)
  • Java 25 toolchain auto-provisioning: settings.gradle.kts applies the foojay-resolver-convention plugin so Gradle downloads and provisions a matching Java 25 JDK automatically when the build machine does not already have one, removing a manual setup step for new contributors
  • Request-batching substrate: New module-internal org.higherkindedj.optics.fetch package: a free-applicative-style Fetch<K, V, A> (Done/Blocked) and FetchApplicative that plug into the optic modifyF seam so a traversal whose focused values are loaded from a backend coalesces those N loads into one batched call (the classic N+1), with a transport- and datastore-neutral BatchLoader contract and round-based runCached/runAsync runners carrying a per-run request cache. The package is intentionally not exported; it is the foundation for later data-access capabilities and carries no public API yet (#539)

Previous: v0.4.6 Next: v0.4.4