_ _ _ _ _ ___ _ _ ___
| | | (_) | | | | / (_) | | | | |_ |
| |_| |_ __ _| |__ ___ _ __ ______| |/ / _ _ __ __| | ___ __| |______ | |
| _ | |/ _` | '_ \ / _ \ '__|______| \| | '_ \ / _` |/ _ \/ _` |______| | |
| | | | | (_| | | | | __/ | | |\ \ | | | | (_| | __/ (_| | /\__/ /
\_| |_/_|\__, |_| |_|\___|_| \_| \_/_|_| |_|\__,_|\___|\__,_| \____/
__/ |
|___/
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.