Series overview
Part 11 of 2055% complete
2026-04-15•15 min read

Kleisli composition for effectful functions

Part 2’s composition worked because the types lined up: A → B feeds B → C. Registry work does not line up that way — every step has the shape A → Result<B, E>. The output carries a context the next function does not accept. Kleisli composition is the answer that already exists: it is ordinary function composition for functions that return a monadic context.

One line of implementation

data/Results.java
public static <A, B, C, E> Fn1<A, Result<C, E>> andThenK(
Fn1<A, Result<B, E>> first,
Fn1<B, Result<C, E>> second) {
return input -> first.apply(input).flatMap(second);
}

Compare with Fn1.andThen: where andThen pipes the plain output into the next function, andThenK pipes it through flatMap — so a Failure from first skips second entirely, and the composition is still a single Fn1 you can compose again.

data/Results.java
public static <A, B, C, E> Fn1<A, Result<C, E>> composeK(
Fn1<B, Result<C, E>> second,
Fn1<A, Result<B, E>> first) {
return andThenK(first, second);
}

Scheme example: the validation chain

LookupChain.java
Fn1<String, Result<CitizenFacts, RegistryError>> loadCitizen = citizens::loadCitizen;
Fn1<CitizenFacts, Result<Tuple2<CitizenFacts, LandRecord>, RegistryError>> loadLand =
facts -> land.loadLandRecord(facts.citizenId())
.map(record -> new Tuple2<>(facts, record));
Fn1<Tuple2<CitizenFacts, LandRecord>, Result<EligibilityOutcome, RegistryError>> evaluate =
pair -> Result.success(FarmerSupportPolicy.evaluate(pair.first(), scheme));
Fn1<String, Result<EligibilityOutcome, RegistryError>> pipeline =
Results.andThenK(loadCitizen, Results.andThenK(loadLand, evaluate));

Each link — load the citizen, fetch their land record, evaluate the policy — is an independently testable Fn1 returning Result. andThenK wires them into one function String → Result<EligibilityOutcome, RegistryError> with no visible branching, because the branching lives inside flatMap where the laws keep it honest.

Why this is not just convenience

The point of naming the pattern is reuse of a verified composition. andThenK is associative whenever flatMap is — and Part 10 tested exactly that. So a chain built by andThenK.andThenK.andThenK regroups freely; the intermediate functions are extractable and nameable without changing meaning, which is precisely what you want when a government audit asks “what are the steps of this decision, in order?” — the code is the answer.

Result<Identity, E>

Result<CitizenFacts, E>

citizenId

validateIdentity

andThenK

loadCitizen

andThenK

evaluate

Result<EligibilityOutcome, E>

Result<Identity, E>

Result<CitizenFacts, E>

citizenId

validateIdentity

andThenK

loadCitizen

andThenK

evaluate

Result<EligibilityOutcome, E>

When not to reach for it: two or three flatMap calls in a row read fine as a fluent chain. andThenK earns its keep when the steps are reused in multiple orders, tested as isolated units, or built dynamically — if your chain is fixed and short, the fluent version is clearer to a reader who has not met the pattern.

JavaFunctional Programming

Type to search the site.

↑↓ navigate⏎ openPowered by Pagefind