Java can abstract over ordinary types, but it cannot directly abstract over a type constructor such as Option<?> in the same way Scala can abstract over F[_].
Milestone 0.5 introduces a small encoding:
public interface Kind<F, A> {
}F is a witness identifying a type constructor and A is the contained value type.
Option<A> now implements:
Kind<OptionK, A>
OptionK is a zero-state witness type. It is not instantiated at runtime.
Option<Integer> option = Option.some(42);
Kind<OptionK, Integer> kind = OptionKinds.widen(option);
Option<Integer> restored = OptionKinds.narrow(kind);Binary ADTs need one parameter fixed before they can act as a unary constructor.
Either<L, R>
↓ fix L
EitherK<L>
↓ apply R
Kind<EitherK<L>, R>
Therefore:
Either<String, Integer>
≈ Kind<EitherK<String>, Integer>
The same technique is used for Validated<E, A>:
Validated<String, Integer>
≈ Kind<ValidatedK<String>, Integer>
widen is safe and requires no cast because each ADT directly implements the corresponding Kind application.
narrow requires an unchecked cast because Java type erasure cannot prove the relationship between a witness and its concrete ADT. Those casts are intentionally isolated in:
OptionKindsEitherKindsValidatedKinds
No other production code should need HKT-related unchecked casts.
This is an encoding, not native higher-kinded type support. Compared with Scala or Haskell it has:
- explicit witness types,
- weaker inference,
- more boilerplate,
- controlled unchecked narrowing.
The benefit is that later milestones can express abstractions such as:
interface Functor<F> {
<A, B> Kind<F, B> map(Kind<F, A> value, Function<? super A, ? extends B> mapper);
}without hard-coding Option, Either, or Validated.
Milestone 0.5 does not implement Functor, Applicative, Monad, Traverse, Kleisli, effect types, or Tagless Final. It establishes only the type-level infrastructure required by those abstractions.