Type classes are a powerful abstraction mechanism popularized by Haskell.
This library implements type class resolution for Java, allowing you to define type classes and their instances (witnesses) in a modular fashion, and summon them automatically at runtime.
For a tutorial-like explanation, see: https://garciat.com/posts/java-type-classes/
The library is published on Maven Central as com.garciat.typeclasses/java-type-classes
For the core resolution model:
<dependency>
<groupId>com.garciat.typeclasses</groupId>
<artifactId>java-type-classes-core</artifactId>
<version>0.1.5</version>
</dependency>For some predefined type classes & instances:
<dependency>
<groupId>com.garciat.typeclasses</groupId>
<artifactId>java-type-classes-instances</artifactId>
<version>0.1.5</version>
</dependency>For the annotation processor:
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>com.garciat.typeclasses</groupId>
<artifactId>java-type-classes-core</artifactId>
<version>0.1.5</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>@interface TypeClass {
@interface Witness {}
}
interfaceTy<T> {} // witness type tokenclassTypeClasses {
static <T> Twitness(Ty<T> ty);
}Where:
- For a witness type
C<T1, T2, ..., Tn>,witness()looks for witness constructors inCandT1, T2, ..., Tn. - Witness constructors for a type
Tare itspublic staticmethods annotated with@TypeClass.Witness. - For a witness constructor
C<T> ctor(D1, D2, ..., Dn), the witness dependenciesD1, D2, ..., Dnare resolved recursively. - Resolution fails when multiple witness constructors exist for a witness type, after applying overlapping instances reduction.
- Resolution fails when a witness constructor for a witness type cannot be found.
- Witness summoning is the result of recursively invoking witness constructors up their respective dependency trees.
T witness(Ty<T>)summons a witness of typeTor fails with a runtime exception of typeTypeClasses.WitnessResolutionException.
// Type class definition@TypeClasspublicinterfaceShow<T> {
Stringshow(Tvalue);
// Helper for inference:static <T> Stringshow(Show<T> showT, Tvalue) {
returnshowT.show(value);
}
// Witness definitions:// "Leaf" witness with no dependencies:@TypeClass.WitnessstaticShow<Integer> integerShow() {
returni -> Integer.toString(i);
}
// Witness with dependencies (constraints):@TypeClass.Witnessstatic <A> Show<List<A>> listShow(Show<A> showA) {
returnlistA -> listA.stream().map(showA::show).collect(Collectors.joining(", ", "[", "]"));
}
}
// Custom typerecordPair<A, B>(Afirst, Bsecond) {
@TypeClass.Witnesspublicstatic <A, B> Show<Pair<A, B>> pairShow(Show<A> showA, Show<B> showB) {
returnpair -> "(" + showA.show(pair.first()) + ", " + showB.show(pair.second()) + ")";
}
}
// UsageclassExample {
voidmain() {
Pair<Integer, List<Integer>> value = newPair<>(1, List.of(2, 3, 4));
// Summon (and use) the Show witness for Pair<Integer, List<Integer>>:Strings = Show.show(witness(newTy<>() {}), value);
System.out.println(s); // prints: (1, [2, 3, 4])
}
}- Support for higher-kinded type classes like
Functor<F<_>>,Monad<M<_>>, etc.- See the
api.hktpackage for details.
- See the
- Support for overlapping instances a la Haskell.
- Based on this GHC spec.
- Annotation processor:
- To reify the witness graph at compile time.
- To support parameterless
witness()calls.
- Caching of summoned witnesses.