PromiseKit
Production Promise library built from algebraic first principles, shipped to ~50K DAU across multiple iOS apps
Overview
PromiseKit is a production Promise/Future library embedded in PovioKit -- a modular Swift Package distributed as 7 independent libraries. It has zero external dependencies and serves as the async backbone: the networking module imports it to expose a promise-based API for all HTTP operations. The library shipped to ~50K daily active users across multiple iOS apps including Dropbox Stacks, evolving over 3+ years through PRs addressing race conditions, synchronization primitives, and concurrency requirements.
Architecture
The design separates read-only observation (Future<Value, Error>) from write capability (Promise<Value>). Future holds the lock, observer list, and result storage as the synchronization core; Promise inherits and adds the public resolve/reject API. Combinators form a proper algebraic hierarchy where complex operations derive from simpler ones -- map from flatMap, reduce from fold -- with only flatMap and flatMapError as fundamental operations.
Key Concepts
Algebraic Combinator Derivation
The combinators form a proper algebraic hierarchy: map derives from flatMap (map = flatMap . pure from Haskell), mapError from flatMapError, fold from and + flatMap, reduce from fold. There are really only two fundamental operations: flatMap and flatMapError. Everything else composes from those two.
Haskell Operator Precedence Groups
Custom Swift precedence groups mirror Haskell's typeclass tower: SequencePrecedence (applicative) > ChoicePrecedence (alternative) > FlatMapPrecedence (monadic bind). This means expressions like `Promise.error(.e) <|> Promise.value(10) >>- { ... }` parse with correct algebraic semantics.
Code Highlights
func flatMap<U>(
on dispatchQueue: DispatchQueue? = .main,
with transform: @escaping (Value) throws -> Promise<U>
) -> Promise<U> {
Promise<U> { seal in
self.finally {
switch $0 {
case .success(let value):
dispatchQueue.async {
do {
try transform(value).finally {
switch $0 {
case .success(let value):
seal.resolve(with: value, on: dispatchQueue)
case .failure(let error):
seal.reject(with: error, on: dispatchQueue)
}
}
} catch { seal.reject(with: error, on: dispatchQueue) }
}
case .failure(let error):
seal.reject(with: error, on: dispatchQueue)
}
}
}
}
// map is derived: map f = flatMap (pure . f)
func map<U>(on dq: DispatchQueue? = .main,
with transform: @escaping (Value) throws -> U) -> Promise<U> {
flatMap(on: dq) { do { return .value(try transform($0)) }
catch { return .error(error) } }
}public func concurrentlyDispatch<T>(
spawnTask next: @escaping (Int) -> Promise<T>?,
concurrent: Int, retryCount: Int = 2,
on dispatchQueue: DispatchQueue? = .main
) -> Promise<()> {
.init { seal in
let barrier = DispatchQueue(label: "barrier", attributes: .concurrent)
var segmentIndex = concurrent
var active = [(promise: Promise<T>, retryCount: Int, segmentIndex: Int)]()
func observer(_ result: Result<T, Error>, arrayIndex: Int) {
barrier.async(flags: .barrier) {
switch result {
case .success:
guard let promise = next(segmentIndex) else {
if active.allSatisfy({ $0.promise.isFulfilled }) {
seal.resolve(on: dispatchQueue) }; return }
promise.finally { observer($0, arrayIndex: arrayIndex) }
active[arrayIndex] = (promise, 0, segmentIndex)
segmentIndex += 1
case .failure where active[arrayIndex].retryCount < retryCount:
let idx = active[arrayIndex].segmentIndex
let promise = next(idx)!
promise.finally { observer($0, arrayIndex: arrayIndex) }
active[arrayIndex] = (promise, active[arrayIndex].retryCount + 1, idx)
case .failure(let error):
active.forEach { $0.promise.reject(with: error) }
seal.reject(with: error, on: dispatchQueue)
}
}
}
// spawn initial pool
for i in 0..<concurrent {
guard let p = next(i) else { break }
active.append((p, 0, i))
}
active.enumerated().forEach { (i, t) in t.promise.finally { observer($0, arrayIndex: i) } }
}
}// Applicative > Alternative > Monad (matches Haskell's typeclass tower)
precedencegroup SequencePrecedence { higherThan: ChoicePrecedence }
precedencegroup ChoicePrecedence { higherThan: FlatMapPrecedence }
precedencegroup FlatMapPrecedence { higherThan: AssignmentPrecedence }
// >>- monadic bind (flatMap infix)
func >>-<T, U>(p: Promise<T>, f: @escaping (T) throws -> Promise<U>) -> Promise<U> {
p.flatMap(on: .main, with: f)
}
// <|> alternative (try left, on failure try right)
func <|><T>(left: Promise<T>, right: @autoclosure @escaping () -> Promise<T>) -> Promise<T> {
left.flatMapError(on: .main) { _ in right() }
}
// <*> applicative apply
func <*><T, U>(left: Promise<(U) -> T>, right: Promise<U>) -> Promise<T> {
right >>- { r in left.map(on: .main) { $0(r) } }
}
// Usage: curry(makeUser) <^> fetchName() <*> fetchEmail() <*> fetchAge()Highlights
- Designed and built a production Promise library from algebraic first principles -- map derives from flatMap, reduce from fold, operators match Haskell's typeclass precedence hierarchy
- Evolved thread safety through production-driven iterations -- migrated to NSLock + barrier dispatch, immediately fixing a race condition exposed by the migration
- Built domain-specific concurrency primitives: bounded work pool with per-slot retry tracking, server-driven dynamic polling, and zero-overhead serial execution
- Implemented genuine Haskell operators in production Swift -- applicative apply, alternative, monadic bind, functor map, plus curry functions up to 6-arity
- Zero-dependency library serving as the async foundation for PovioKit's networking layer across all Povio Labs apps
Related Projects
fluent-html
Zero-dependency, type-safe HTML builder for TypeScript with compile-time Tailwind CSS and HTMX safety
Designed a mixin architecture using TypeScript declaration merging + prototype assignment that splits a 1,100-line Tag class into focused modules with zero API changes
Redis Server (Zig)
From-scratch Redis-compatible server in Zig with RESP parsing, RDB persistence, and master-replica replication
Implemented a complete Redis-compatible server from scratch in 915 lines of zero-dependency Zig with RESP protocol parser, key-value store with TTL, and RDB binary format codec
Algorithm Competitions
Two 1st places and one 2nd/3rd place across three timed MSc algorithm competitions
1st place — Progressive Chess: A* converted to greedy best-first (g=0), Zobrist hashing with incremental XOR, composable per-piece heuristic dispatch, pawn promotion pruning