_ _ _ _ _ ___ _ _ ___
| | | (_) | | | | / (_) | | | | |_ |
| |_| |_ __ _| |__ ___ _ __ ______| |/ / _ _ __ __| | ___ __| |______ | |
| _ | |/ _` | '_ \ / _ \ '__|______| \| | '_ \ / _` |/ _ \/ _` |______| | |
| | | | | (_| | | | | __/ | | |\ \ | | | | (_| | __/ (_| | /\__/ /
\_| |_/_|\__, |_| |_|\___|_| \_| \_/_|_| |_|\__,_|\___|\__,_| \____/
__/ |
|___/
Bringing Higher-Kinded Types to Java functional patterns
Higher-Kinded-J brings popular functional patterns to Java by providing implementations of common Monads and Transformers supporting Higher-Kinded Types.
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.
Introduction: Abstracting Over Computation in Java
Java's type system excels in many areas, but it lacks native support for Higher-Kinded Types (HKTs). This means we cannot easily write code that abstracts over type constructors like List<A>
, Optional<A>
, or CompletableFuture<A>
in the same way we abstract over the type A
itself. We can't easily define a generic function that works identically for any container or computational context (like List, Optional, Future, IO).
Higher-Kinded-J simulates HKTs in Java using a technique inspired by defunctionalisation. It allows you to define and use common functional abstractions like Functor
, Applicative
, and Monad
(including MonadError
) in a way that works generically across different simulated type constructors.
Why bother? Higher-Kinded-J unlocks several benefits:
- Write Abstract Code: Define functions and logic that operate polymorphically over different computational contexts (e.g., handle optionality, asynchronous operations, error handling, side effects, or collections using the same core logic).
- Leverage Functional Patterns: Consistently apply powerful patterns like
map
,flatMap
,ap
,sequence
,traverse
, and monadic error handling (raiseError
,handleErrorWith
) across diverse data types. - Build Composable Systems: Create complex workflows and abstractions by composing smaller, generic pieces, as demonstrated in the included Order Processing Example.
- Understand HKT Concepts: Provides a practical, hands-on way to understand HKTs and type classes even within Java's limitations.
While Higher-Kinded-J introduces some boilerplate compared to languages with native HKT support, it offers a valuable way to explore these powerful functional programming concepts in Java.
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.
You can apply the patterns and techniques from Higher-Kinded-J in many ways:
- Generic Utilities: Write utility functions that work across different monadic types (e.g., a generic
sequence
function to turn aList<Kind<F, A>>
into aKind<F, List<A>>
). - Composable Workflows: Structure complex business logic, especially involving asynchronous steps and error handling (like the Order Example), in a more functional and composable manner.
- Managing Side Effects: Use the
IO
monad to explicitly track and sequence side-effecting operations. - Deferred Computation: Use the
Lazy
monad for expensive computations that should only run if needed. - Dependency Injection: Use the
Reader
monad to manage dependencies cleanly. - State Management: Use the
State
monad for computations that need to thread state through. - Logging/Accumulation: Use the
Writer
monad to accumulate logs or other values alongside a computation. - Learning Tool: Understand HKTs, type classes (Functor, Applicative, Monad), and functional error handling concepts through concrete Java examples.
- Simulating Custom Types: Follow the pattern (Kind interface, Holder if needed, Helper, Type Class instances) to make your own custom data types or computational contexts work with the provided functional abstractions.
To understand and use Higher-Kinded-J effectively, explore these documents:
- Core Concepts: Understand the fundamental building blocks –
Kind
, Witness Types, Type Classes (Functor
,Monad
, etc.), and the helper classes that bridge the simulation with standard Java types. Start here! - Supported Types: See which Java types (like
List
,Optional
,CompletableFuture
) and custom types (Maybe
,Either
,Try
,IO
,Lazy
) are currently simulated and have corresponding type class instances. - Usage Guide: Learn the practical steps involved in using Higher-Kinded-J: obtaining type class instances, wrapping/unwrapping values using helpers, and applying type class methods (
map
,flatMap
, etc.). - Order Example Walkthrough: Dive into a detailed, practical example showcasing how
EitherT
(a monad transformer) combinesCompletableFuture
(for async) andEither
(for domain errors) to build a robust workflow. This demonstrates a key use case. - Extending Higher-Kinded-J: Learn the pattern for adding Higher-Kinded-J support and type class instances for your own custom Java types or other standard library types.
How to Use Higher-Kinded-J (In Your Project)
You could adapt Higher-Kinded-J for use in your own projects:
- Include the dependency: The relevant packages (
org.higherkindedj.hkt
and the packages for the types you need, e.g.,org.higherkindedj.hkt.optional
) are available from
// latest gradle release
dependencies {
implementation("io.github.higher-kinded-j:higher-kinded-j:0.1.5")
}
// alternatively if you want to try a SNAPSHOT
repositories {
mavenCentral()
maven {
url= uri("https://central.sonatype.com/repository/maven-snapshots/")
}
}
dependencies {
implementation("io.github.higher-kinded-j:higher-kinded-j:0.1.6-SNAPSHOT")
}
- Understand the Pattern: Familiarise yourself with the
Kind
interface, the specificKind
interfaces (e.g.,OptionalKind
), theKindHelper
classes (e.g.,OptionalKindHelper
), and the type class instances (e.g.,OptionalMonad
). - Follow the Usage Guide: Apply the steps outlined in the Usage Guide to wrap your Java objects, obtain monad instances, use
map
/flatMap
/etc., and unwrap the results. - Extend if Necessary: If you need HKT simulation for types not included, follow the guide in Extending the Simulation.