Products — When Two Independent Things Become One Structure
In programming, we combine values all the time.
But rarely do we ask a deeper question:
Is there a correct way to combine things?
Not just a convenient way. Not just a common pattern. But a way that is universally right.
Category theory answers this using something called a universal construction. Instead of defining a structure directly, it defines it by how it relates to everything else.
And when we ask:
“What is the best way to combine two things?”
This perspective leads us to a fundamental idea:
The Product.
A product is not just a pair of values. It is the *most universal* way of combining them.
To make this concrete, consider something simple:
Latitude and longitude. Individually, they are just numbers. But when combined correctly, they represent a precise location on Earth. And when combined incorrectly… everything breaks.
So what does it mean to combine them correctly?
That’s exactly what the idea of a Product captures.
The Mathematical Idea
In category theory, a product of A and B is:
An object (A, B) with two projections:
π₁ : (A, B) → A
π₂ : (A, B) → BThese just mean:
The Pattern (What really matters)
We don’t define product by structure.
We define it by behavior:
c
/ \
p q
/ \
A BKey Idea
If you have:
p : C → A
q : C → BThen there must exist a unique function:
m : C → (A, B)Factorizer (The Heart of Product)
This function is called the factorizer:
m(x) = (p(x), q(x))Kotlin Implementation
fun <C, A, B> factorizer(
p: (C) -> A,
q: (C) -> B
): (C) -> Pair<A, B> = { x ->
Pair(p(x), q(x))
}Real Example — Latitude & Longitude
Domain
data class Location(
val latitude: Double,
val longitude: Double
)Projections
val getLatitude: (Location) -> Double = { it.latitude }
val getLongitude: (Location) -> Double = { it.longitude }Type matches, meaning is wrong(Compiles!)
val wrong = { loc: Location ->
Pair(getLatitude(loc), getLatitude(loc)) // bug
}Output:
(12.97, 12.97)Here Longitude lost, Type system didn’t help
Correct (Factorizer)
val correct = factorizer(getLatitude, getLongitude)
println(correct(Location(12.97, 77.59)))
// (12.97, 77.59)Why Factorizer Matters
It enforces laws:
fst(m(x)) = p(x)
snd(m(x)) = q(x)Any function that breaks this is not a product
| Without Product | With Product |
|---|---|
| Many ways to combine | Only ONE valid way |
| Easy bugs | Guaranteed correctness |
| No structure | Mathematical guarantee |
Real Use Case — Parallel Data Fetch
A screen needs to show user info along with their posts.
Define Combine
fun <C, A, B> combine(
f: (C) -> A,
g: (C) -> B
): (C) -> Pair<A, B> = { x ->
Pair(f(x), g(x))
}Use combine
val fetchAll = combine(
::fetchUser,
::fetchPosts
)
val result = fetchAll(userId)We are combining two independent computations into one.
Parallel Version
fun <C, A, B> combineAsync(
f: suspend (C) -> A,
g: suspend (C) -> B
): suspend (C) -> Pair<A, B> = { x ->
coroutineScope {
val a = async { f(x) }
val b = async { g(x) }
Pair(a.await(), b.await())
}
}val fetchAll = combineAsync(
::fetchUser,
::fetchPosts
)
val result = fetchAll(userId)When values are independent but needed together, they form a Product.
Final Takeaway
A product is not just a pair.
It is:
A structure where **every valid combination must pass through one unique path**
One-line Conclusion
Product is not about combining values — it’s about guaranteeing the **only correct way to combine them**
