Damian Lattenero
03/19/2026, 5:29 PM// Three primitives — that's the whole model:
// .ap { } → parallel ("and at the same time...")
// .followedBy { } → barrier ("then, once that's done...")
// .flatMap { } → barrier that passes data forward
val checkout = Async {
lift7(::CheckoutResult)
.ap { fetchUser(userId) } // ┐
.ap { fetchCart(userId) } // ├─ phase 1: all parallel
.ap { fetchInventory(userId) } // ┘
.followedBy { validateStock() } // ── barrier: waits for phase 1
.ap { calcShipping() } // ┐ phase 2: parallel
.ap { calcTax() } // ┘
.followedBy { reservePayment() } // ── barrier
.ap { sendConfirmation() } // phase 3
}
// Swap two .ap lines → compiler error.
// 7 service calls, 3 phases, one flat chain.
// When phase 2 NEEDS phase 1 results → flatMap:
val dashboard = Async {
lift3(::UserContext)
.ap { fetchProfile(id) } // ┐ phase 1
.ap { fetchPrefs(id) } // │
.ap { fetchLoyalty(id) } // ┘
.flatMap { ctx -> // barrier + passes ctx forward
lift2(::Dashboard)
.ap { recommendations(ctx.profile) } // ┐ phase 2
.ap { promotions(ctx.loyalty) } // ┘
}
}
Available on Maven Central: io.github.damian-rafael-lattenero:kap:1.0.0
https://github.com/damian-rafael-lattenero/coroutines-applicatives
Feedback welcome!CLOVIS
03/20/2026, 9:01 AMDamian Lattenero
03/20/2026, 12:19 PM.ap (parallel), .followedBy (barrier), and .flatMap (barrier that passes data). That's the entire mental model.
The rest Schedule, Resource, CircuitBreaker, settled, raceQuorum are opt-in utilities that naturally come up when you use the core in production. You need retries, timeouts, resource cleanup, partial failure tolerance. They're all independent you can use just lift + ap + followedBy and ignore everything else.
I actually just rewrote the README to make this clearer, with a Before/After comparison for every feature (raw coroutines vs Arrow vs this library): https://github.com/damian-rafael-lattenero/coroutines-applicatives
Would love to hear what you think after a fresh look!CLOVIS
03/20/2026, 1:13 PMDamian Lattenero
03/20/2026, 1:26 PMkotlinx-coroutines-core. Most Kotlin projects don't use Arrow, so I didn't want to gate the library behind that.
The builtin Either Nel Schedule and Resource are minimal and purpose built for orchestration. For projects that already use Arrow, theres an arrow-interop module that bridges the types so no duplication in practice.
CircuitBreaker lives in core because it chains directly with Computation (.withCircuitBreaker(b).retry(s).recover { ... }). That composability is the point.CLOVIS
03/20/2026, 1:37 PMCLOVIS
03/20/2026, 1:38 PMDamian Lattenero
03/20/2026, 2:00 PMkap-core w/ just the Applicative DSL + Computation, super lean… and then kap-resilience as opt-in for Schedule/Resource/CircuitBreaker stuff.
and for Arrow users, a kap-arrow module makes more sense — just bridge straight into Arrow types instead of duplicating logic (that usually ends up messy + harder to maintain anyway).
that way you only pick what you actually need, nothing else bloating your dep graph. gonna look into this for the next version… thx for pushing on it, architecturally it’s def the right call 👍. I can start applying these changesCLOVIS
03/20/2026, 2:03 PMDamian Lattenero
03/23/2026, 9:12 PMCLOVIS
03/24/2026, 9:29 AMlift1 , lift2 , lift3 etc. That naming is familiar to Haskell etc developers, but is not common in the JVM ecosystem. I would suggest a name that's less mathematics and more explicit on what it does, eg.
kap(::Dashboard)
.param { fetchUser() }
.param { fetchCart() }
// etc
That may be too different from your vision though.
Still, you don't need the 1 , 2 , 3 postfixes, the compiler can distinguish between overloads since the number of type parameters is different. You may need a @JvmName . See this example which has a much more complex signature.CLOVIS
03/24/2026, 9:34 AMDamian Lattenero
03/24/2026, 5:54 PMlift1, lift2, lift3
Single name: kap. The compiler resolves the overload by function arity (Function2, Function3, etc.) — no numeric suffixes, no `@JvmName`:
kap(::Pair) // compiler picks kap<P1, P2, R>
kap(::CheckoutResult) // compiler picks kap<P1, ..., P11, R>
// works up to 22 params
Kotlin Multiplatform (JVM + JS + Native), so function types are enough to distinguish.
2. .with instead of .ap
Reads like English — "kap this constructor with user, with cart, with promos":
val checkout: CheckoutResult = Async {
kap(::CheckoutResult)
.with { fetchUser() } // ┐
.with { fetchCart() } // ├─ phase 1: parallel
.with { fetchPromos() } // │
.with { fetchInventory() } // ┘
.followedBy { validateStock() } // ── phase 2: barrier
.with { calcShipping() } // ┐
.with { calcTax() } // ├─ phase 3: parallel
.with { calcDiscounts() } // ┘
.followedBy { reservePayment() } // ── phase 4: barrier
.with { generateConfirmation() } // ┐ phase 5
.with { sendEmail() } // ┘
}
3. Your question about executing in a different order than the constructor
"What if I want to compute the last two parameters first, then a barrier, then the rest?"Two approaches depending on whether you need a barrier or not: If you just need different assembly order (no barrier) — all 4 run in parallel, you assemble the constructor however you want:
data class Page(val a: String, val b: String, val c: String, val d: String)
val result = Async {
combine(
pair({ fetchC() }, { fetchD() }),
pair({ fetchA() }, { fetchB() }),
) { (c, d), (a, b) -> Page(a, b, c, d) }
}
// All 4 run in parallel. Constructor receives them in its own order.
If you need a barrier between phases — the computation {} builder is the cleanest way. Each .bind() acts as a barrier:
val result = Async {
computation {
// Phase 1: c and d in parallel
val (c, d) = pair({ fetchC() }, { fetchD() }).bind()
// ── barrier (bind waits for phase 1) ──
// Phase 2: a and b in parallel
val (a, b) = pair({ fetchA() }, { fetchB() }).bind()
// Assemble in constructor order
Page(a, b, c, d)
}
}
Within each pair(...) calls run in parallel, each .bind() is a barrier, and at the end you assemble the constructor in whatever order it expects. No nesting, no workarounds.
Both examples compile and run verified in the repo.
Full updated README + code: https://github.com/damian-rafael-lattenero/coroutines-applicativesRobert Jaros
03/25/2026, 5:21 AMDamian Lattenero
03/27/2026, 5:37 PMDamian Lattenero
03/29/2026, 6:44 PM