This is not a full fledged package for functional programming in Swift. This will have to wait until Higher Kinded Types are part of the language. However this will make it easier to write functional code using built-in Swift Result and Optional types.
A lightweight functional programming toolkit for Swift, providing composable utilities for working with Result, Optional, and Array types in both synchronous and asynchronous contexts.
- Swift 6.2+
- macOS 10.15+ / iOS 13+ / tvOS 13+ / watchOS 6+ / visionOS 1+ / Linux
- The
mapAsyncKeepOrderfamily requires macOS 15+ / iOS 18+ / tvOS 18+ / watchOS 11+ / visionOS 2+ (no version gate on Linux) withTimeoutrequires macOS 13+ / iOS 16+ / tvOS 16+ / watchOS 9+ / visionOS 1+ (it takes aDuration)
Every platform is exercised in CI: macOS 15 and 26, Ubuntu Linux, and the iOS, tvOS, watchOS, and visionOS simulators.
Add the package to your Package.swift:
dependencies: [
.package(url: "https://github.com/velocityzen/fp-swift.git", from: "3.0.0")
]Then import it:
import FPForward pipe operators for function composition:
// Basic pipe: pass value to function
let result = 5 |> double |> toString // "10"
// Pipe into second argument
let result = value |>> (function, firstArg)
// Pipe into third argument
let result = value |>>> (function, firstArg, secondArg)
// Flow operator: create function from another function
let transform = |> double // (Int) -> IntPipe operators bind looser than arithmetic, ranges, casts, and ??, but tighter than comparisons, logical operators, and assignment — the same placement as Elixir's |> and F#'s pipe family. The full expression on the left flows into the function on the right:
1 + 2 |> double // double(3) = 6 — not 1 + double(2)
x |> transform == 6 // (x |> transform) == 6
flag && x |> isValid // flag && (x |> isValid)
a ?? b |> process // (a ?? b) |> processAll pipe operators (including the prefix flow operator) have async overloads. Sync and async functions can be freely mixed in a chain — the chain becomes async as soon as any step is async:
// Mixed sync/async chain — only one `await` needed at the front
let summary = await userId
|> normalizeId // sync (Int) -> Int
|> fetchUser // async (Int) async -> User
|> renderSummary // sync (User) -> String
// Two- and three-arg variants
let profile = await userId |>> (fetchProfile, session)
let html = await body |>>> (renderPage, title, theme)
// Async point-free
let load: (Int) async -> User = |> fetchUserAsyncAdding the async overloads does not affect existing sync usage: in a sync context (no await) only the sync overload can match, and in an async context Swift still prefers the sync overload when the closure is sync.
func createOrder(userId: Int, itemId: Int) -> Result<Order, AppError> {
ResultDo<AppError>()
.bind { fetchUser(id: userId) }
.bind { user in fetchItem(id: itemId) }
.let { user, item in item.price * user.discountRate }
.bind { user, item, price in
validateOrder(user: user, item: item, price: price)
}
.map { user, item, price, validation in
Order(user: user, item: item, price: price)
}
}
// Async variant with mixed sync/async steps
func createOrderAsync(userId: Int, itemId: Int) async -> Result<Order, AppError> {
await ResultDo<AppError>()
.bindAsync { await fetchUser(id: userId) }
.bindAsync { user in await fetchItem(id: itemId) }
.let { user, item in item.price * user.discountRate }
.bindAsync { user, item, price in
await validateOrder(user: user, item: item, price: price)
}
.map { user, item, price, validation in
Order(user: user, item: item, price: price)
}
}func processUser(id: Int) async -> Result<ProcessedUser, Error> {
await Result.fromAsync { try await api.fetchUser(id: id) }
.tapAsync { user in await analytics.track(.userFetched(user)) }
.mapAsync { user in await enrichUserData(user) }
.flatMapAsync { user in await validateUser(user) }
.tapError { error in logger.error("Failed: \(error)") }
}let result: Result<Int, Error> = .success(42)
// Transform success value asynchronously
let mapped = await result.mapAsync { value in
await fetchData(for: value)
}
// FlatMap for chaining Result-returning async operations
let chained = await result.flatMapAsync { value in
await validateAndTransform(value) // Returns Result<T, Error>
}
// Create Result from async throwing operation
let result = await Result.fromAsync {
try await networkCall()
}
// Create Result from Task
let task = Task { try await networkCall() }
let result = await Result.fromTask(task)
// Or with closure syntax
let result = await Result.fromTask {
Task { try await networkCall() }
}
// Also works with Tasks returning Results
let task = Task { await someResultOperation() }
let result: Result<Value, Error> = await Result.fromTask(task)Replace the success value with a constant or discard it entirely:
let result: Result<Int, AppError> = .success(42)
// Map to a specific constant
let mapped = result.as("done") // .success("done")
// Map to Void (discard the success value)
let unit = result.asUnit() // .success(())
// Useful in chains where you only care about success/failure
fetchUser(id: 1)
.tap { user in saveToCache(user) }
.asUnit() // Result<Void, AppError>Perform side effects while keeping the Result chain flowing:
someOperation()
.tap { value in saveToCache(value) }
.tapError { error in logError(error) }
.map { value in transform(value) }
// Async variants
await result
.tapAsync { value in await sendAnalytics(value) }
.tapErrorAsync { error in await reportError(error) }Tap variants:
.tap()- Sync side effect on success.tapAsync()- Async side effect on success.tapError()- Sync side effect on failure.tapErrorAsync()- Async side effect on failure
Throwing variants convert the Failure type to Error; if the action throws, the thrown error becomes the failure (in the tapError case it replaces the original failure). Result-returning variants propagate the action's failure the same way.
If self is a success, keep it; otherwise return the lazily-evaluated alternative. The alternative may itself succeed (recovery) or fail (in which case its failure replaces the original):
fetchUser(id: 1)
.alt { fetchUserFromCache(id: 1) }
.alt { .success(.guest) }
// Async variant
await fetchUser(id: 1)
.altAsync { await fetchUserFromCache(id: 1) }Returns the success value, or computes/returns a fallback when the result is a failure. Unlike alt, this returns the unwrapped Success rather than another Result:
// Closure-based — receives the error
let count = parse(input).getOrElse { _ in 0 }
// Constant default (lazily evaluated)
let count = parse(input).getOrElse(0)
// Async variant — closure receives the error
let user = await fetchUser(id: 1).getOrElseAsync { _ in
await loadGuestUser()
}
// Async variant — lazily-evaluated async default
let user = await fetchUser(id: 1).getOrElseAsync(await loadGuestUser())Designed for CLI entry points. Returns the success value, or prints the failure to stderr and calls Foundation.exit:
// Default: prints "Error: <error>\n" and exits with status 1
let config = loadConfig().getOrExit()
// Customize the prefix and exit code
let port = parsePort(args).getOrExit(prefix: "fatal: ", exitCode: 2)
// Provide a fully custom message — include any trailing newline yourself
let user = fetchUser(id: 1).getOrExit { error in
"could not load user: \(error)\n"
}Same termination behavior as getOrExit, but discards the success value. Useful at the end of a pipeline that has already consumed the success:
runCommand(args)
.tap { output in print(output) }
.orExit()
// Custom message
runCommand(args).orExit { error in
"command failed: \(error)\n"
}Branch on a Result without switching manually:
let message = result.match(
{ "value: \($0)" },
{ "error: \($0)" }
)
let fallback = result.match("ok", "error")
let mixed = result.match(
{ "value: \($0)" },
"error"
)
let asyncMessage = await result.matchAsync(
{ value in
await Task.yield()
return "value: \(value)"
},
{ error in
await Task.yield()
return "error: \(error)"
}
)
let asyncMixed = await result.matchAsync(
"ok",
{ error in
await Task.yield()
return "error: \(error)"
}
)
// Use match for side effects without capturing the result
result.match(
{ value in print("Success: \(value)") },
{ error in print("Error: \(error)") }
)Convert optionals to Results:
// With static error
let result = Result<User, AppError>.fromOptional(user, error: .notFound)
// With lazy error (only evaluated if nil)
let result = Result<User, AppError>.fromOptional(user) {
.notFound(id: userId)
}Boolean checks for simple conditionals:
let result: Result<User, AppError> = fetchUser(id: 1)
if result.isSuccess {
print("User fetched successfully")
}
// Or in ternary expressions
let message = result.isSuccess ? "ok" : "error"
if result.isFailure {
scheduleRetry()
}Combine multiple Results into a single Result with a tuple of values:
let userResult: Result<User, AppError> = fetchUser(id: 1)
let profileResult: Result<Profile, AppError> = fetchProfile(id: 1)
let settingsResult: Result<Settings, AppError> = fetchSettings(id: 1)
// Sync flatten - combine already-computed Results
let combined = flatten(userResult, profileResult, settingsResult)
// Result<(User, Profile, Settings), AppError>
// Use map to create named tuple for easier access
let namedResult = flatten(userResult, profileResult)
.map { (user: $0, profile: $1) }
// Result<(user: User, profile: Profile), AppError>
if case .success(let data) = namedResult {
print(data.user.name)
print(data.profile.bio)
}Run multiple async operations in parallel and combine their results:
func loadDashboard(userId: Int) async -> Result<Dashboard, AppError> {
// All three operations run in parallel
let result = await withAll(
await fetchUser(id: userId),
await fetchNotifications(for: userId),
await fetchRecommendations(for: userId)
)
// Result<(User, [Notification], [Recommendation]), AppError>
return result.map { user, notifications, recommendations in
Dashboard(user: user, notifications: notifications, recommendations: recommendations)
}
}Run several async operations concurrently and take the first one to finish with a success — not merely the first to finish. A fast .failure is passed over while the rest keep running, so a slower success still wins. The winner cancels the remaining operations; if every operation fails, the last failure to complete is returned.
func fastestMirror(for path: String) async -> Result<Data, NetworkError> {
// Whichever mirror responds successfully first wins; the others are cancelled.
await withAny(
await fetch(from: primaryMirror, path),
await fetch(from: backupMirror, path),
await fetch(from: archiveMirror, path)
)
}Operations are taken as autoclosures (the same ergonomics as withAll), so calls are deferred and raced rather than evaluated up front. The variadic form supports 2–10 operations; to race a number known only at runtime, pass an array of closures:
let attempts: [@Sendable () async -> Result<Data, NetworkError>] =
mirrors.map { mirror in { await fetch(from: mirror, path) } }
let data = await withAny(attempts) // must be non-emptyCancellation of the losers is cooperative: a long racer that never checks Task.isCancelled still runs to completion before withAny returns.
Run an async operation with a deadline. If it finishes within duration, its Result is returned; otherwise the operation is cancelled and .failure(timeoutError) is returned. A failure that arrives before the timeout is not swallowed — it's returned as-is.
func loadProfile(id: Int) async -> Result<Profile, AppError> {
await withTimeout(.seconds(5), failingWith: .timeout) {
await fetchProfile(id: id)
}
}It's a race between the operation and a timer — whichever finishes first wins — so this isn't withAny (which waits for the first success); a fast failure here returns immediately. Cancellation is cooperative: when the timeout fires, the operation stops promptly only if it observes Task.isCancelled. Because it takes a Duration, withTimeout requires macOS 13+ / iOS 16+ / tvOS 16+ / watchOS 9+ / visionOS 1+.
Apply a Result-returning transform to each element, short-circuiting on first failure:
let userIds = [1, 2, 3]
// Sync traverse
let result = userIds.traverse { id -> Result<User, AppError> in
fetchUser(id: id)
}
// Returns .success([User]) or .failure on first error
// Async traverse — sequential: each element awaits the previous one,
// and elements after a failure never run
let result = await userIds.traverseAsync { id in
await fetchUserAsync(id: id)
}
// Parallel traverse — up to `concurrency` transforms in flight at once.
// Results keep source order; fails with the lowest-index failure and
// cancels in-flight transforms cooperatively.
let result = await userIds.traverseAsync(concurrency: 4) { id in
await fetchUserAsync(id: id)
}Both traverseAsync(concurrency:) and mapAsyncKeepOrder run transforms in parallel with ordered output. Use traverseAsync for an all-or-nothing pass over a finite array — one Result at the end, the lowest-index failure wins, a failure cancels the rest. Use mapAsyncKeepOrder when you want each result as soon as order allows, have an endless source, or want failures kept in position in the stream.
Asynchronous versions of map, flatMap, and compactMap:
let items = [1, 2, 3, 4, 5]
let mapped = await items.mapAsync { item in
"v\(item)"
}
let flattened = await items.flatMapAsync { item in
[item, item * 10]
}
let compacted = await items.compactMapAsync { item -> String? in
await processItem(item) // Returns nil for items to filter out
}
enum ParseError: Error {
case invalid
}
let resultFlattened = await items.flatMapAsync { item -> Result<[Int], ParseError> in
.success([item, item + 100])
}
let resultCompacted = await items.compactMapAsync { item -> Result<String?, ParseError> in
.success(item.isMultiple(of: 2) ? "even-\(item)" : nil)
}Process streams of Results with familiar functional operations:
let stream = AsyncStream<Result<Int, AppError>> { continuation in
continuation.success(1)
continuation.success(2)
continuation.failure(.invalid)
continuation.success(3)
continuation.finish()
}
// Filter to just success values
for await value in stream.successes() {
print(value) // 1, 2, 3
}
// Transform, tap, and chain
for await result in stream
.tap { value in logger.info("got \(value)") }
.tapError { error in logger.error("\(error)") }
.mapAsync { value in await enrich(value) }
.flatMap { value in validate(value) }
{
// ...
}Map elements through an async transform while preserving source order. By default transforms run one at a time — pass concurrency greater than 1 to opt into parallelism, bounding wall-clock time by the slowest element rather than the sum of all transforms while emission still follows source arrival order. If the consumer stops iterating early, no further elements are read from the source and in-flight transforms are cancelled cooperatively. Useful when an upstream provider streams items that should be processed concurrently but consumed in order (e.g. SSE image references that need to be fetched in parallel and rendered in order):
for await image in references.mapAsyncKeepOrder(concurrency: 8, { ref in
await downloader.fetch(ref)
}) {
render(image)
}For an all-or-nothing parallel pass over a finite array, see traverseAsync(concurrency:) in Traverse instead.
When the source is a stream of Result, an overload transforms only the success values and passes failures through unchanged — preserving order across both:
for await result in events.mapAsyncKeepOrder({ event in
await enrich(event)
}) {
handle(result) // Result<EnrichedEvent, MyError>
}Create single-element Result streams or use convenience methods on continuations:
// Static factories — single-element streams
let success: AsyncStream<Result<Int, MyError>> = .success(42)
let failure: AsyncStream<Result<Int, MyError>> = .failure(.someError)
// Continuation helpers
let stream = AsyncStream<Result<Int, MyError>> { continuation in
continuation.success(1)
continuation.success(2)
continuation.failure(.someError)
continuation.finish()
}
// Or finish with a final value
let stream = AsyncStream<Result<String, MyError>> { continuation in
continuation.success("processing...")
continuation.finishWithSuccess("done") // Yields and finishes
}
// Finish with error
let stream = AsyncStream<Result<Data, NetworkError>> { continuation in
continuation.finishWithFailure(.connectionLost) // Yields error and finishes
}let optional: String? = "hello"
let message = optional.match(
{ "got: \($0)" },
"nothing"
)
// "got: hello"
let missing: String? = nil
missing.match(
{ value in print(value) },
()
)
// Does nothinglet optional: Int? = 42
let mapped = await optional.mapAsync { value in
await fetchDetails(for: value)
}
// Returns nil if optional was nil, otherwise the transformed value
let flatMapped = await optional.flatMapAsync { value -> String? in
value > 0 ? "id-\(value)" : nil
}let optional: Int? = nil
let fallback = optional.orElse(99) // 99 — first .some wins, so nil falls through
let kept = Optional(5).orElse(99) // 5 — keeps the wrapped value
// getOrElse unwraps with a default (equivalent to ??)
let count = optional.getOrElse(0) // 0Monadic do-notation for composing multiple Result operations with an accumulating context:
// ResultDo starts the chain, bind adds Result values, let adds pure values
let result = ResultDo<MyError>()
.bind { getUser() } // Result<User, MyError>
.bind { user in getProfile(user) } // Result<(User, Profile), MyError>
.let { _, profile in profile.name } // Result<(User, Profile, String), MyError>
.map { user, _, name in "\(user.id): \(name)" } // Result<String, MyError>
// Short-circuits on the first failure
let result = ResultDo<MyError>()
.bind { getUser() } // .failure(.notFound) → stops here
.bind { user in getProfile(user) } // never called
.map { user, profile in profile } // never called
// result == .failure(.notFound)API:
// Start the chain
ResultDo<Failure>()
// Bind: add a Result value, accumulates into a growing tuple
func bind<A>(_ f: () -> Result<A, Failure>) -> Result<A, Failure>
func bind<B>(_ f: (A) -> Result<B, Failure>) -> Result<(A, B), Failure>
func bind<C>(_ f: (A, B) -> Result<C, Failure>) -> Result<(A, B, C), Failure>
// ... up to 10 accumulated values
// Let: add a pure (non-Result) value
func `let`<A>(_ f: () -> A) -> Result<A, Failure>
func `let`<B>(_ f: (A) -> B) -> Result<(A, B), Failure>
func `let`<C>(_ f: (A, B) -> C) -> Result<(A, B, C), Failure>
// ... up to 10 accumulated values
// Async variants: bindAsync / letAsync
func bindAsync<A>(_ f: () async -> Result<A, Failure>) async -> Result<A, Failure>
func bindAsync<B>(_ f: (A) async -> Result<B, Failure>) async -> Result<(A, B), Failure>
// ... up to 10 accumulated values
func letAsync<A>(_ f: () async -> A) async -> Result<A, Failure>
func letAsync<B>(_ f: (A) async -> B) async -> Result<(A, B), Failure>
// ... up to 10 accumulated valuesSync and async can be freely mixed in the same chain:
let result = await ResultDo<MyError>()
.bind { getCachedUser() } // sync
.bindAsync { user in await fetchProfile(user) } // async
.let { user, profile in profile.name } // sync
.mapAsync { user, profile, name in // async
await formatDisplay(user, name)
}// Non-throwing
func mapAsync<T>(_ transform: (Success) async -> T) async -> Result<T, Failure>
func mapErrorAsync<E: Error>(_ transform: (Failure) async -> E) async -> Result<Success, E>
func flatMapAsync<T>(_ transform: (Success) async -> Result<T, Failure>) async -> Result<T, Failure>
// Throwing (requires Failure == Error)
func mapAsync<T>(_ transform: (Success) async throws -> T) async -> Result<T, Error>
// Map to constant value
func `as`<T>(_ value: T) -> Result<T, Failure>
func asUnit() -> Result<Void, Failure>// Lazily provides an alternative on failure
func alt(_ alternative: () -> Result<Success, Failure>) -> Result<Success, Failure>
func altAsync(_ alternative: () async -> Result<Success, Failure>) async -> Result<Success, Failure>// Unwrap or fall back
func getOrElse(_ onFailure: (Failure) -> Success) -> Success
func getOrElse(_ defaultValue: @autoclosure () -> Success) -> Success
func getOrElseAsync(_ onFailure: (Failure) async -> Success) async -> Success
func getOrElseAsync(_ defaultValue: @autoclosure @escaping () async -> Success) async -> Success// Unwrap or print to stderr and call Foundation.exit
func getOrExit(prefix: String = "Error: ", exitCode: Int32 = 1) -> Success
func getOrExit(exitCode: Int32 = 1, message: (Failure) -> String) -> Success
// Discard success; print to stderr and exit on failure
func orExit(prefix: String = "Error: ", exitCode: Int32 = 1)
func orExit(exitCode: Int32 = 1, message: (Failure) -> String)
All match variants are marked @discardableResult, so you can use them both for transforming values and for side effects without assigning the result.
@discardableResult func match<T>(_ onSuccess: (Success) -> T, _ onFailure: (Failure) -> T) -> T
@discardableResult func match<T>(_ onSuccess: (Success) -> T, _ failure: @autoclosure () -> T) -> T
@discardableResult func match<T>(_ success: @autoclosure () -> T, _ onFailure: (Failure) -> T) -> T
@discardableResult func match<T>(_ success: @autoclosure () -> T, _ failure: @autoclosure () -> T) -> T
@discardableResult func matchAsync<T>(_ onSuccess: (Success) async -> T, _ onFailure: (Failure) async -> T) async -> T
@discardableResult func matchAsync<T>(_ onSuccess: (Success) async -> T, _ failure: @autoclosure () -> T) async -> T
@discardableResult func matchAsync<T>(_ success: @autoclosure () -> T, _ onFailure: (Failure) async -> T) async -> T// Throwing (requires Failure == Error)
static func fromAsync(_ operation: () async throws -> Success) async -> Result<Success, Error>// Throwing Task (requires Failure == Error)
static func fromTask(_ task: Task<Success, Error>) async -> Result<Success, Error>
static func fromTask(_ task: () -> Task<Success, Error>) async -> Result<Success, Error>
// Non-throwing Task (requires Failure == Never)
static func fromTask(_ task: Task<Success, Never>) async -> Result<Success, Never>
static func fromTask(_ task: () -> Task<Success, Never>) async -> Result<Success, Never>
// Task returning Result (requires Failure == Error)
static func fromTask<S>(_ task: Task<Result<S, Error>, Never>) async -> Result<S, Error>
static func fromTask<S>(_ task: () -> Task<Result<S, Error>, Never>) async -> Result<S, Error>static func fromOptional(_ optional: Success?, error: Failure) -> Result<Success, Failure>
static func fromOptional(_ optional: Success?, onError: () -> Failure) -> Result<Success, Failure>
static func fromOptional(error: Failure) -> (Success?) -> Result<Success, Failure>
static func fromOptional(onError: () -> Failure) -> (Success?) -> Result<Success, Failure>var isSuccess: Bool // true for success, false for failure
var isFailure: Bool // true for failure, false for successThrowing variants convert Failure to Error; a thrown error becomes the failure. Result-returning variants keep the success value but propagate the action's failure (the action's Failure type must match the Result's).
// Non-throwing
func tap(_ action: (Success) -> Void) -> Result<Success, Failure>
func tap<T>(_ action: (Success) -> T) -> Result<Success, Failure>
// Result-returning
func tap(_ action: (Success) -> Result<Void, Failure>) -> Result<Success, Failure>
func tap<T>(_ action: (Success) -> Result<T, Failure>) -> Result<Success, Failure>
// Throwing
func tap(_ action: (Success) throws -> Void) -> Result<Success, Error>
func tap<T>(_ action: (Success) throws -> T) -> Result<Success, Error>
// Async non-throwing
func tapAsync(_ action: (Success) async -> Void) async -> Result<Success, Failure>
func tapAsync<T>(_ action: (Success) async -> T) async -> Result<Success, Failure>
// Async Result-returning
func tapAsync(_ action: (Success) async -> Result<Void, Failure>) async -> Result<Success, Failure>
func tapAsync<T>(_ action: (Success) async -> Result<T, Failure>) async -> Result<Success, Failure>
// Async throwing
func tapAsync(_ action: (Success) async throws -> Void) async -> Result<Success, Error>
func tapAsync<T>(_ action: (Success) async throws -> T) async -> Result<Success, Error>In the throwing variants, a thrown error replaces the original failure. In the Result-returning variants, the action's failure replaces the original; its success is discarded.
// Non-throwing
func tapError(_ action: (Failure) -> Void) -> Result<Success, Failure>
func tapError<T>(_ action: (Failure) -> T) -> Result<Success, Failure>
// Result-returning
func tapError<T>(_ action: (Failure) -> Result<T, Failure>) -> Result<Success, Failure>
// Throwing
func tapError(_ action: (Failure) throws -> Void) -> Result<Success, Error>
func tapError<T>(_ action: (Failure) throws -> T) -> Result<Success, Error>
// Async non-throwing
func tapErrorAsync(_ action: (Failure) async -> Void) async -> Result<Success, Failure>
func tapErrorAsync<T>(_ action: (Failure) async -> T) async -> Result<Success, Failure>
// Async Result-returning
func tapErrorAsync<T>(_ action: (Failure) async -> Result<T, Failure>) async -> Result<Success, Failure>
// Async throwing
func tapErrorAsync(_ action: (Failure) async throws -> Void) async -> Result<Success, Error>
func tapErrorAsync<T>(_ action: (Failure) async throws -> T) async -> Result<Success, Error>// Runs regardless of success or failure, returns self unchanged
func finally(_ action: () -> Void) -> Result<Success, Failure>
func finallyAsync(_ action: () async -> Void) async -> Result<Success, Failure>Combine multiple Results into a single Result containing a tuple of all success values. If any Result fails, returns the first failure.
// Supports 2-10 arguments
func flatten<A, B, E: Error>(_ a: Result<A, E>, _ b: Result<B, E>) -> Result<(A, B), E>
func flatten<A, B, C, E: Error>(_ a: Result<A, E>, _ b: Result<B, E>, _ c: Result<C, E>) -> Result<(A, B, C), E>
// ... up to 10 arguments// Supports 2-10 arguments, runs all operations in parallel
func withAll<A: Sendable, B: Sendable, E: Error>(
_ a: @Sendable @autoclosure @escaping () async -> Result<A, E>,
_ b: @Sendable @autoclosure @escaping () async -> Result<B, E>
) async -> Result<(A, B), E>
// ... up to 10 argumentswithAny runs async operations concurrently and returns the first to succeed, cancelling the rest. A failure that finishes first is passed over; if all fail, the last failure to complete is returned. At least one operation is required.
// Variadic (autoclosure), 2-10 operations — same ergonomics as withAll
func withAny<Success: Sendable, Failure: Error>(
_ a: @Sendable @autoclosure @escaping () async -> Result<Success, Failure>,
_ b: @Sendable @autoclosure @escaping () async -> Result<Success, Failure>
) async -> Result<Success, Failure>
// ... up to 10 arguments
// Array form for a dynamic number of operations (must be non-empty)
func withAny<Success: Sendable, Failure: Error>(
_ operations: [@Sendable () async -> Result<Success, Failure>]
) async -> Result<Success, Failure>withTimeout runs one operation, returning its result if it finishes within duration, or .failure(timeoutError) if it doesn't — cancelling the operation. A failure that arrives before the timeout is returned as-is, not swallowed. Requires macOS 13+ / iOS 16+ (for Duration).
@available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, visionOS 1.0, *)
func withTimeout<Success: Sendable, Failure: Error>(
_ duration: Duration,
failingWith timeoutError: Failure,
_ operation: @Sendable @escaping () async -> Result<Success, Failure>
) async -> Result<Success, Failure>func traverse<Success>(_ transform: (Element) -> Success) -> Result<[Success], Never>
func traverse<Success, Failure>(_ transform: (Element) -> Result<Success, Failure>) -> Result<[Success], Failure>
// Sequential — each element awaits the previous one
func traverseAsync<Success>(_ transform: (Element) async -> Success) async -> Result<[Success], Never>
func traverseAsync<Success, Failure>(_ transform: (Element) async -> Result<Success, Failure>) async -> Result<[Success], Failure>
// Parallel — up to `concurrency` in flight, results in source order,
// lowest-index failure wins, in-flight transforms cancelled on failure
func traverseAsync<Success: Sendable>(
concurrency: Int,
_ transform: @Sendable @escaping (Element) async -> Success
) async -> Result<[Success], Never> where Element: Sendable
func traverseAsync<Success: Sendable, Failure: Error>(
concurrency: Int,
_ transform: @Sendable @escaping (Element) async -> Result<Success, Failure>
) async -> Result<[Success], Failure> where Element: Sendable// Split an array of Results into both sides
func separate<Success, Failure>() -> (successes: [Success], failures: [Failure])
where Element == Result<Success, Failure>
// Just one side
func successes<Success, Failure>() -> [Success] where Element == Result<Success, Failure>
func failures<Success, Failure>() -> [Failure] where Element == Result<Success, Failure>// Drop nil elements
func compact<T>() -> [T] where Element == T?
// All-or-nothing: nil if any element is nil
func sequence<T>() -> [T]? where Element == T?// mapAsync — for Result-returning transforms, use traverseAsync
func mapAsync<T>(_ transform: (Element) async -> T) async -> [T]
// flatMapAsync
func flatMapAsync<S: Sequence>(
_ transform: (Element) async -> S
) async -> [S.Element]
func flatMapAsync<S: Sequence, Failure: Error>(
_ transform: (Element) async -> Result<S, Failure>
) async -> Result<[S.Element], Failure>
// compactMapAsync
func compactMapAsync<T>(_ transform: (Element) async -> T?) async -> [T]
func compactMapAsync<T, Failure: Error>(
_ transform: (Element) async -> Result<T?, Failure>
) async -> Result<[T], Failure>Extensions for any AsyncSequence where Element == Result<Success, Failure>:
func successes() -> AsyncCompactMapSequence // unwraps success values
func failures() -> AsyncCompactMapSequence // unwraps failure errors// Sync
func map<T>(_ transform: (Success) -> T) -> AsyncMapSequence<Self, Result<T, Failure>>
func mapError<E>(_ transform: (Failure) -> E) -> AsyncMapSequence<Self, Result<Success, E>>
func flatMap<T>(_ transform: (Success) -> Result<T, Failure>) -> AsyncMapSequence<Self, Result<T, Failure>>
// Async
func mapAsync<T>(_ transform: (Success) async -> T) -> AsyncMapSequence<Self, Result<T, Failure>>
func mapErrorAsync<E>(_ transform: (Failure) async -> E) -> AsyncMapSequence<Self, Result<Success, E>>
func flatMapAsync<T>(_ transform: (Success) async -> Result<T, Failure>) -> AsyncMapSequence<Self, Result<T, Failure>>// Sync
func tap(_ action: (Success) -> Void) -> AsyncMapSequence<Self, Result<Success, Failure>>
func tapError(_ action: (Failure) -> Void) -> AsyncMapSequence<Self, Result<Success, Failure>>
// Async
func tapAsync(_ action: (Success) async -> Void) -> AsyncMapSequence<Self, Result<Success, Failure>>
func tapErrorAsync(_ action: (Failure) async -> Void) -> AsyncMapSequence<Self, Result<Success, Failure>>Element transforms run at most concurrency at a time — sequential by default (concurrency: 1), parallel when you raise it; output preserves source arrival order either way. Early termination by the consumer cancels in-flight transforms cooperatively.
// General form
func mapAsyncKeepOrder<T: Sendable>(
concurrency: Int = 1,
_ transform: @Sendable @escaping (Element) async -> T
) -> AsyncStream<T>
where Self: Sendable, Failure == Never, Element: Sendable
// Result overload — transforms successes, passes failures through
func mapAsyncKeepOrder<Success: Sendable, E: Error, T: Sendable>(
concurrency: Int = 1,
_ transform: @Sendable @escaping (Success) async -> T
) -> AsyncStream<Result<T, E>>
where Self: Sendable, Failure == Never, Element == Result<Success, E>
// Fallible transform — transform failures replace the success they came from
func flatMapAsyncKeepOrder<Success: Sendable, E: Error, T: Sendable>(
concurrency: Int = 1,
_ transform: @Sendable @escaping (Success) async -> Result<T, E>
) -> AsyncStream<Result<T, E>>
where Self: Sendable, Failure == Never, Element == Result<Success, E>Create single-element Result streams:
static func success<Success, Failure>(_ value: Success) -> AsyncStream<Result<Success, Failure>>
static func failure<Success, Failure>(_ error: Failure) -> AsyncStream<Result<Success, Failure>>Extensions for AsyncStream.Continuation when the element type is Result<Success, Failure>:
// Yield success/failure values
func success<Success, Failure>(_ value: Success) -> YieldResult
func failure<Success, Failure>(_ error: Failure) -> YieldResult
// Yield and finish the stream
func finishWithSuccess<Success, Failure>(_ value: Success)
func finishWithFailure<Success, Failure>(_ error: Failure)@discardableResult func match<T>(_ onSome: (Wrapped) -> T, _ onNone: @autoclosure () -> T) -> T
@discardableResult func matchAsync<T>(_ onSome: (Wrapped) async -> T, _ onNone: @autoclosure () -> T) async -> Tfunc mapAsync<T>(_ transform: (Wrapped) async -> T) async -> T?
func flatMapAsync<T>(_ transform: (Wrapped) async -> T?) async -> T?// First .some wins; the alternative is only evaluated when nil
func orElse(_ alternative: @autoclosure () -> Wrapped?) -> Wrapped?
func orElseAsync(_ alternative: @autoclosure @escaping () async -> Wrapped?) async -> Wrapped?
// Unwrap with a default (equivalent to ??)
func getOrElse(_ defaultValue: @autoclosure () -> Wrapped) -> Wrapped
func getOrElseAsync(_ defaultValue: @autoclosure @escaping () async -> Wrapped) async -> Wrapped// Runs regardless of .some/.none, returns self unchanged
func finally(_ action: () -> Void) -> Wrapped?
func finallyAsync(_ action: () async -> Void) async -> Wrapped?MIT