_ _ _ _ _ ___ _ _ ___
| | | (_) | | | | / (_) | | | | |_ |
| |_| |_ __ _| |__ ___ _ __ ______| |/ / _ _ __ __| | ___ __| |______ | |
| _ | |/ _` | '_ \ / _ \ '__|______| \| | '_ \ / _` |/ _ \/ _` |______| | |
| | | | | (_| | | | | __/ | | |\ \ | | | | (_| | __/ (_| | /\__/ /
\_| |_/_|\__, |_| |_|\___|_| \_| \_/_|_| |_|\__,_|\___|\__,_| \____/
__/ |
|___/
Bringing Higher-Kinded Types and Optics to Java functional patterns
Higher-Kinded-J brings two powerful functional programming toolsets to Java, enabling developers to write more abstract, composable, and robust code.
Higher-Kinded-J provides:
- A Higher-Kinded Types (HKT) Simulation to abstract over computational contexts like
Optional
,List
, orCompletableFuture
. - A powerful Optics Library to abstract over immutable data structures, with boilerplate-free code generation.
These two pillars work together to solve common problems in a functional, type-safe way.
Two Pillars of Functional Programming
1: A Higher-Kinded Types Simulation ⚙️
Java's type system lacks native support for Higher-Kinded Types, making it difficult to write code that abstracts over "container" types. We can't easily define a generic function that works identically for List<A>
, Optional<A>
, and CompletableFuture<A>
.
Higher-Kinded-J simulates HKTs in Java using a technique inspired by defunctionalisation. This unlocks the ability to use common functional abstractions like Functor
, Applicative
, and Monad
generically across different data types.
With HKTs, you can:
- Abstract Over Context: Write logic that works polymorphically over different computational contexts (optionality, asynchrony, error handling, collections).
- Leverage Typeclasses: Consistently apply powerful patterns like
map
,flatMap
,sequence
, andtraverse
across diverse data types. - Manage Effects: Use provided monads like
IO
,Either
,Validated
, andState
to build robust, composable workflows.
2: A Powerful Optics Library 🔎
Working with immutable data structures, like Java records, is great for safety but leads to verbose "copy-and-update" logic for nested data.
Higher-Kinded-J provides a full-featured Optics library that treats data access as a first-class value. An optic is a composable, functional getter/setter that lets you "zoom in" on a piece of data within a larger structure.
With Optics, you can:
- Eliminate Boilerplate: An annotation processor generates
Lens
,Prism
, andTraversal
optics for your records and sealed interfaces automatically. - Perform Deep Updates Effortlessly: Compose optics to create a path deep into a nested structure and perform immutable updates in a single, readable line.
- Decouple Data and Operations: Model your data cleanly as immutable records, while defining complex, reusable operations separately as optics.
- Perform Effectful Updates: The Optics library is built on top of the HKT simulation, allowing you to perform failable, asynchronous, or stateful updates using the powerful
modifyF
method.
Getting Started
note
Before diving in, ensure you have the following: Java Development Kit (JDK): Version 24 or later. The library makes use of features available in this version.
The project is modular. To use it, add the relevant dependencies to your build.gradle
or pom.xml
. The use of an annotation processor helps to automatically generate the required boilerplate for Optics and other patterns.
For HKTs:
// build.gradle.kts
implementation("io.github.higher-kinded-j:hkj-core:LATEST_VERSION")
For Optics:
// build.gradle.kts
implementation("io.github.higher-kinded-j:hkj-core:LATEST_VERSION")
annotationProcessor("io.github.higher-kinded-j:hkj-processor:LATEST_VERSION")
annotationProcessor("io.github.higher-kinded-j:hkj-processor-plugins:LATEST_VERSION")
For SNAPSHOTS:
repositories {
mavenCentral()
maven {
url= uri("https://central.sonatype.com/repository/maven-snapshots/")
}
}
Documentation
We recommend following the documentation in order to get a full understanding of the library's capabilities.
Optics Guides
This series provides a practical, step-by-step introduction to solving real-world problems with optics.
- An Introduction to Optics: Learn what optics are and the problems they solve.
- Practical Guide: Lenses: A deep dive into using
Lens
for nested immutable updates. - Practical Guide: Prisms: Learn how to use
Prism
to safely work withsealed interface
(sum types). - Practical Guide: Isos: Understand how
Iso
provides a bridge between equivalent data types. - Practical Guide: Traversals: Master the
Traversal
for performing bulk updates on collections. - Capstone Example: Deep Validation: A complete example that composes multiple optics to solve a complex problem.
HKT Core Concepts
For users who want to understand the underlying HKT simulation that powers the optics library or use monads directly.
- An Introduction to HKTs: Learn what HKTs are and the problems they solve.
- Core Concepts: Understand
Kind
, Witness Types, and Type Classes (Functor
,Monad
). - Supported Types: See which types are simulated and have typeclass instances.
- Usage Guide: Learn the practical steps for using the HKT simulation directly.
- Examples of how to use HKTs: Practical Examples of how to use the Monads.
- Order Example Walkthrough: A detailed example of building a robust workflow with monad transformers.
- Extending Higher-Kinded-J: Learn how to add HKT support for your own custom types.
History
Higher-Kinded-J evolved from a simulation that was originally created for the blog post Higher Kinded Types with Java and Scala that explored Higher-Kinded types and their lack of support in Java. The blog post discussed a process called defuctionalisation that could be used to simulate Higher-Kinded types in Java. Since then Higher-Kinded-J has grown into something altogether more useful supporting more functional patterns.