Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,42 @@

All notable changes to this educational project are documented here.

## 0.5.0

### Added

- minimal higher-kinded type encoding `Kind<F,A>`,
- documented `hkt` package,
- `OptionK`, `EitherK<L>` and `ValidatedK<E>` witness types,
- `OptionKinds`, `EitherKinds` and `ValidatedKinds`,
- centralized `widen` / `narrow` conversions,
- HKT round-trip unit tests,
- `HktExample`,
- `docs/HKT.md`.

### Changed

- project version updated from `0.4.1` to `0.5.0`,
- `Option<A>` now implements `Kind<OptionK,A>`,
- `Either<L,R>` now implements `Kind<EitherK<L>,R>`,
- `Validated<E,A>` now implements `Kind<ValidatedK<E>,A>`,
- project description, README, ADT documentation, architecture and roadmap updated for the HKT layer.

### Removed / intentionally omitted

- no `Kind2` hierarchy is introduced because partial witness types are sufficient for the next milestones,
- no `Functor`, `Applicative`, `Monad`, `Traverse`, effect type, Kleisli or Tagless Final implementation is added yet,
- no reflection, annotation processing, registry or runtime witness objects are introduced,
- no obsolete scripts or generated build outputs are retained.

### Design notes

- witness types contain no runtime state and exist only to identify type constructors,
- binary ADTs use partial type application by fixing one parameter in the witness type,
- widening is cast-free because the ADTs implement `Kind` directly,
- unavoidable unchecked narrowing is isolated in exactly three helper methods,
- the milestone intentionally establishes infrastructure rather than higher-order behavior, following SOLID, KISS, DRY and YAGNI.

## 0.4.1

### Added
Expand Down
144 changes: 64 additions & 80 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

An educational implementation of typeclass-oriented functional programming in modern Java.

Milestone **0.4.1** hardens the ADT layer introduced in 0.4.0 with non-empty collections, smart constructors, typed validation errors and domain invariants.
Milestone **0.5.0** introduces a minimal higher-kinded type (HKT) encoding on top of the ADT and algebra layers completed in 0.4.1.

## Goals

Expand All @@ -15,12 +15,13 @@ The project currently covers:
- executable algebraic laws and jqwik property tests,
- sealed ADTs: `Option`, `Either` and `Validated`,
- compositional `Eq` and `Show` instances,
- `NonEmptyList<A>`,
- smart constructors and invariant-safe domain values,
- typed validation errors,
- domain-level error accumulation with `Validated`.
- `NonEmptyList<A>` and lawful concatenation `Semigroup`,
- smart constructors, typed validation errors and domain invariants,
- a minimal HKT encoding through `Kind<F,A>`,
- witness types and partial type application,
- centralized widen/narrow helpers.

The project deliberately does **not** yet introduce HKT encoding, `Functor`, `Applicative`, `Monad`, `Traverse`, effects, Kleisli, Tagless Final, Free Monad or optics.
The project deliberately does **not** yet introduce `Functor`, `Applicative`, `Monad`, `Traverse`, effect types, Kleisli, Tagless Final, Free Monad or optics.

## Requirements

Expand All @@ -47,136 +48,119 @@ src/main/java/io/codeswarm/typeclasses/
│ ├── Eq.java
│ ├── Ord.java
│ └── Show.java
├── hkt/
│ ├── Kind.java
│ └── package-info.java
├── data/
│ ├── Option.java
│ ├── OptionK.java
│ ├── OptionKinds.java
│ ├── Some.java
│ ├── None.java
│ ├── Either.java
│ ├── EitherK.java
│ ├── EitherKinds.java
│ ├── Left.java
│ ├── Right.java
│ ├── Validated.java
│ ├── ValidatedK.java
│ ├── ValidatedKinds.java
│ ├── Valid.java
│ ├── Invalid.java
│ └── NonEmptyList.java
├── domain/
│ ├── ValidationError.java
│ ├── BlankUserId.java
│ ├── BlankPersonName.java
│ ├── PersonNameTooLong.java
│ ├── InvalidAge.java
│ ├── UserId.java
│ ├── PersonName.java
│ ├── Age.java
│ ├── User.java
│ └── UserValidator.java
├── examples/
├── instances/
└── syntax/
```

Reusable law helpers remain in `src/test/java/io/codeswarm/typeclasses/laws/`.

## NonEmptyList
## Higher-kinded type encoding

`NonEmptyList<A>` encodes non-emptiness structurally:
Java can abstract over values of type `A`, but cannot directly abstract over a type constructor like Scala's `F[_]`. Milestone 0.5 introduces:

```java
NonEmptyList<Integer> values = NonEmptyList.of(1, 2, 3);
```

There is no public way to create an empty value. This lets `Invalid<E,A>` represent failed validation without a separate runtime `isEmpty()` guard.

```java
public record Invalid<E, A>(NonEmptyList<E> errors)
implements Validated<E, A> {
public interface Kind<F, A> {
}
```

`NonEmptyList<A>` has a natural concatenation `Semigroup` but intentionally no `Monoid`:
The `F` parameter is a witness identifying the constructor.

```text
List<A> -> Monoid<List<A>>
NonEmptyList<A> -> Semigroup<NonEmptyList<A>>
```

A monoid requires an identity element; an empty `NonEmptyList` cannot exist.
### Option

## Smart constructors
`Option<A>` now implements:

Domain values expose factories that validate raw input and return an ADT instead of throwing for expected validation failures.

```java
Either<ValidationError, UserId> id = UserId.from(" user-42 ");
Either<ValidationError, Age> age = Age.from(42);
```text
Kind<OptionK, A>
```

Invalid input is explicit:
Example:

```java
Age.from(-1); // Left(InvalidAge[-1])
UserId.from(" "); // Left(BlankUserId[])
Option<Integer> option = Option.some(42);
Kind<OptionK, Integer> widened = OptionKinds.widen(option);
Option<Integer> restored = OptionKinds.narrow(widened);
```

The constructors of `UserId`, `PersonName` and `Age` are private, so invalid states cannot be created through their public APIs.
### Either and partial type application

See [docs/SMART_CONSTRUCTORS.md](docs/SMART_CONSTRUCTORS.md).
`Either<L,R>` has two type parameters, but a future `Functor<F>` needs a unary constructor. The left type is therefore fixed in a witness:

## Typed validation errors
```text
Either<String, Integer>
≈ Kind<EitherK<String>, Integer>
```

Expected validation failures are modeled as a closed ADT:
Likewise:

```text
ValidationError
├── BlankUserId
├── BlankPersonName
├── PersonNameTooLong
└── InvalidAge
Validated<String, Integer>
≈ Kind<ValidatedK<String>, Integer>
```

This is preferable to using arbitrary strings when callers need to inspect or pattern-match on error categories.
This models partial type application without adding wrapper objects around the ADTs.

## Validated and error accumulation
## Widening and narrowing

`UserValidator` demonstrates accumulation of independent errors:
`widen` requires no cast because each ADT directly implements the relevant `Kind` application.

```java
Validated<ValidationError, User> result =
UserValidator.validate("", "", -10);
```
`narrow` needs a controlled unchecked cast because Java type erasure cannot prove the witness-to-ADT relationship at runtime. Those casts are intentionally isolated in exactly three helpers:

The result contains three typed errors in a `NonEmptyList`.
- `OptionKinds.narrow`,
- `EitherKinds.narrow`,
- `ValidatedKinds.narrow`.

This implementation is intentionally domain-specific. Generic validation composition is deferred until the project has `Applicative`.
The rest of the codebase should not perform HKT-related unchecked casts.

## ADT helper factories
See [docs/HKT.md](docs/HKT.md).

Milestone 0.4.1 adds small Java-interoperability helpers:
## Existing ADT and domain layer

```java
Option.when(condition, supplier);
Either.cond(condition, rightSupplier, leftSupplier);
Either.fromNullable(value, leftSupplier);
Either.fromOptional(optional, leftSupplier);
```
Milestone 0.5 preserves the 0.4.1 APIs:

No `flatMap` is added yet; monadic operations will be introduced together with the generic `Monad` milestone.
- `NonEmptyList<A>` models non-empty collections structurally,
- `Invalid<E,A>` stores `NonEmptyList<E>`,
- `UserId`, `PersonName` and `Age` use smart constructors,
- `ValidationError` is a sealed typed error hierarchy,
- `UserValidator` demonstrates domain-level error accumulation with `Validated`.

No monadic API is added to those types yet.

## Design principles

- **SRP** — ADTs, domain validation, typeclasses, instances, syntax and laws remain separate.
- **OCP** — new instances and domain validators do not require modifying typeclasses.
- **DIP** — generic functions continue to depend on typeclass abstractions.
- **KISS** — smart constructors use ordinary Java and explicit ADTs.
- **DRY** — law helpers and compositional instances are reused rather than duplicated.
- **YAGNI** — no HKT, generic Applicative validation or effect system is introduced early.
- **SRP** — HKT mechanics live in `hkt` and `*Kinds` helpers rather than inside unrelated typeclasses.
- **OCP** — new type constructors can participate by introducing their own witness and `Kind` implementation.
- **DIP** — future higher-order typeclasses can depend on `Kind<F,A>` instead of concrete ADTs.
- **KISS** — only one marker abstraction is introduced; there is no `Kind2`, registry or reflection layer.
- **DRY** — unavoidable casts are centralized instead of repeated across future typeclass instances.
- **YAGNI** — `Functor`, `Applicative` and `Monad` remain outside this milestone.

## Examples

`AdtExample` demonstrates `Option`, `Either`, `Validated` and composed `Show` instances.

`SmartConstructorExample` demonstrates validated domain values.
`HktExample` demonstrates round-trip widening and narrowing for `Option`, `Either` and `Validated`.

`ValidatedUserExample` demonstrates accumulated typed validation errors.
Existing examples continue to demonstrate algebra, ADTs, smart constructors and validation.

## Documentation

Expand All @@ -186,14 +170,14 @@ No `flatMap` is added yet; monadic operations will be introduced together with t
- [Algebraic laws](docs/LAWS.md)
- [Algebraic data types](docs/ADT.md)
- [Smart constructors](docs/SMART_CONSTRUCTORS.md)
- [Higher-kinded type encoding](docs/HKT.md)
- [Implementation tasks](docs/IMPLEMENTATION_TASKS.md)
- [Roadmap](docs/ROADMAP.md)
- [Changelog](CHANGELOG.md)

## Roadmap

```text
0.5 HKT encoding: Kind<F,A>
0.6 Functor / Applicative / Monad / MonadError
0.7 Foldable / Traverse / Bifunctor
0.8 Id / Eval / IO / FunctionK
Expand Down
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ plugins {
}

group = 'io.codeswarm'
version = '0.4.1'
version = '0.5.0'

description = 'Educational implementation of typeclass-oriented programming, algebraic data types, smart constructors, domain invariants, compositional instances, and executable algebraic laws in modern Java.'
description = 'Educational implementation of typeclass-oriented programming, algebraic data types, higher-kinded type encoding, compositional instances, smart constructors, and executable algebraic laws in modern Java.'

java {
toolchain {
Expand Down
14 changes: 14 additions & 0 deletions docs/ADT.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,17 @@ ADT results are also used to create domain values safely. See [SMART_CONSTRUCTOR
## Algebraic connection

`NonEmptyList` has a lawful concatenation `Semigroup` but no lawful empty identity. An ordinary `List` can have a concatenation `Monoid` because `List.of()` is an identity element.

## HKT participation from milestone 0.5

The ADTs keep their ordinary Java APIs while also implementing the higher-kinded encoding:

```text
Option<A> -> Kind<OptionK,A>
Either<L,R> -> Kind<EitherK<L>,R>
Validated<E,A> -> Kind<ValidatedK<E>,A>
```

This does not add runtime wrappers and does not alter `map`, `fold`, smart constructors or validation behavior. It only makes the type constructors addressable by generic abstractions that will be introduced in later milestones.

`Either` and `Validated` demonstrate partial type application: their left/error parameter is fixed in the witness type so the remaining value parameter can occupy the `A` position of `Kind<F,A>`.
50 changes: 50 additions & 0 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,53 @@ The accumulation in `UserValidator` is intentionally concrete. A generic solutio
Domain examples depend on ADTs. ADTs do not depend on domain classes. Typeclasses do not depend on either domain examples or specific instances.

This preserves DIP and keeps the educational abstractions reusable.

## Milestone 0.5 — HKT layer

Milestone 0.5 adds a small type-level layer without changing the runtime representation of the ADTs.

```text
Kind<F,A>
▲
┌────────────────┼─────────────────┐
│ │ │
│ │ │
Option<A> Either<L,A> Validated<E,A>
│ │ │
│ │ │
OptionK EitherK<L> ValidatedK<E>
```

Conceptually:

```text
Option<A> ≈ Kind<OptionK, A>
Either<L,A> ≈ Kind<EitherK<L>, A>
Validated<E,A> ≈ Kind<ValidatedK<E>, A>
```

### Boundary of unsafe narrowing

Java erasure means the compiler cannot reconstruct a concrete ADT from `Kind<F,A>` purely from the witness parameter. The unavoidable casts are therefore centralized in:

```text
OptionKinds.narrow
EitherKinds.narrow
ValidatedKinds.narrow
```

This is an explicit architectural boundary: future higher-order typeclass implementations may call those helpers, but should not introduce additional HKT casts.

### Dependency direction

```text
higher-order typeclasses (0.6+)
│
▼
Kind<F,A>
▲
│
concrete ADTs + witnesses
```

`Kind` depends on no ADT. Concrete ADTs opt into the encoding. Future generic abstractions can therefore depend on `Kind` rather than concrete data types.
Loading
Loading