Series overview
Part 9 of 2045% complete
2026-04-12•25 min read

Monads: sequencing dependent steps

The monad has the worst ratio of mystery to substance in all of programming. The substance: flatMap (also called bind) is the operation for sequencing a step whose result decides what happens next. Citizen lookup produces facts; whether you then query the land registry depends on those facts. That dependence — each step chosen by the previous step’s output — is what separates monadic sequencing from applicative combination.

Why map is not enough

Suppose the registry returns Result and the mapper also returns Result:

Result<CitizenFacts, RegistryError> facts = registry.loadCitizen(citizenId);
// next step needs the CitizenFacts, and itself produces a Result:
// facts.map(facts -> land.loadLandRecord(facts.citizenId()))
// : Result<Result<LandRecord, RegistryError>, RegistryError> ← nested, useless

map wraps the mapper’s output in another context, so the failure channel doubles. flatMap collapses the nesting by being defined as “map, then flatten”:

flatMap : M<A> → (A → M<B>) → M<B>

The implementations, again

You already saw them inside the records — look at them now as the monad’s two cases:

data/Result.java
// Success.flatMap: the chain continues
public <R> Result<R, E> flatMap(Fn1<? super T, ? extends Result<R, E>> mapper) {
return mapper.apply(value);
}
// Failure.flatMap: the chain is already over
public <R> Result<R, E> flatMap(Fn1<? super T, ? extends Result<R, E>> mapper) {
return new Failure<>(error);
}

A failure anywhere upstream means every later flatMap is a no-op pass-through — the short-circuit is structural, not an if you have to remember to write.

Scheme example: the dependent chain

scheme/DecisionPipeline.java
public Result<EligibilityOutcome, RegistryError> evaluate(String citizenId) {
return citizens.loadCitizen(citizenId)
.flatMap(facts -> land.loadLandRecord(citizenId)
.map(record -> FarmerSupportPolicy.evaluate(facts, scheme)));
}

citizenId

loadCitizen

Result<CitizenFacts, E>

flatMap

loadLandRecord

Result<LandRecord, E>

map

Result<EligibilityOutcome, E>

citizenId

loadCitizen

Result<CitizenFacts, E>

flatMap

loadLandRecord

Result<LandRecord, E>

map

Result<EligibilityOutcome, E>

The last step is map, not flatMap, because evaluate returns a plain EligibilityOutcome — the rule of thumb is the one from the series outline: map when the function returns an ordinary value, flatMap when it returns a context.

Where the word comes from

Moggi (1989) proposed monads as a way to give denotational semantics to effectful programs; Wadler (1990s) carried them into Haskell as the practical mechanism for I/O and state. The “monad” itself is the pair of operations — pure (wrap a plain value) and flatMap — satisfying the three laws Part 10 tests. Every type you have built that has a lawful pure/flatMap pair — Option, Result, and later IO, Reader, State — is one.

The pitfall the chapter exists for: do not reach for flatMap reflexively. Independent checks deserve Validated (Part 8); a chain where no step consumes the previous result is applicative-shaped, and forcing it through bind just hides that you could have run the steps in parallel — which matters in Part 18.

JavaFunctional Programming

Type to search the site.

↑↓ navigate⏎ openPowered by Pagefind