What is Functional Programming in Swift?
Functional programming (FP) is a programming paradigm that treats computation as the evaluation of mathematical functions and avoids changing state and mutable data. In Swift, functional programming means leveraging Swift's first-class function support, closures, and powerful collection methods to write code that is declarative, predictable, and easier to reason about.
At its core, functional programming in Swift revolves around several key principles:
- Immutability — favoring constants (
let) over variables (var) and avoiding shared mutable state - Pure functions — functions that always produce the same output for the same input and have no side effects
- First-class functions — treating functions as values that can be passed as arguments, returned from other functions, and stored in variables
- Higher-order functions — functions that take other functions as parameters or return functions as their result
- Declarative style — describing what you want to achieve rather than how to achieve it step by step
Pure Functions vs. Impure Functions
A pure function relies only on its input parameters, produces no side effects, and always returns the same result given the same arguments. Here's a comparison:
// ❌ Impure function — relies on external mutable state
var globalCounter = 0
func incrementAndReturn() -> Int {
globalCounter += 1 // Side effect: modifies external state
return globalCounter
}
// ✅ Pure function — depends only on its inputs
func add(_ a: Int, _ b: Int) -> Int {
return a + b // No side effects, always same output for same input
}
// ❌ Impure function — has side effects
func saveUserToDisk(user: User) {
// Writes to disk — a side effect
let data = try JSONEncoder().encode(user)
try data.write(to: fileURL)
}
// ✅ Pure function — transforms input, no side effects
func formatUserName(_ name: String) -> String {
return name.trimmingCharacters(in: .whitespaces).capitalized
}
Pure functions are easier to test, debug, and parallelize because they don't depend on external state that might change unpredictably.
Immutability with let
Swift encourages immutability through its let keyword. Immutable values eliminate entire categories of bugs related to shared mutable state:
// ❌ Mutable approach — error-prone
var userBalance = 100.0
func applyDiscount(price: inout Double, discount: Double) {
price -= discount // Mutates the original value
}
applyDiscount(price: &userBalance, discount: 15.0)
// userBalance is now 85.0 — hard to track where changes happened
// ✅ Immutable approach — predictable
let userBalance = 100.0
func discountedPrice(from price: Double, discount: Double) -> Double {
return price - discount // Returns a new value, original stays unchanged
}
let newBalance = discountedPrice(from: userBalance, discount: 15.0)
// userBalance is still 100.0, newBalance is 85.0 — clear and traceable
First-Class Functions and Closures
In Swift, functions are first-class citizens. You can assign them to variables, pass them as arguments, and return them from other functions. Closures — self-contained blocks of functionality — are the primary mechanism for this:
// Assigning a function to a variable
func square(_ x: Int) -> Int { return x * x }
let mathOperation: (Int) -> Int = square
mathOperation(5) // Returns 25
// Assigning a closure to a variable
let cube: (Int) -> Int = { x in return x * x * x }
cube(3) // Returns 27
// Passing a closure as an argument
func performOperation(_ operation: (Int) -> Int, on value: Int) -> Int {
return operation(value)
}
let result = performOperation(cube, on: 4) // Returns 64
// Returning a function from a function
func makeMultiplier(by factor: Int) -> (Int) -> Int {
return { number in number * factor }
}
let triple = makeMultiplier(by: 3)
triple(7) // Returns 21
Why Functional Programming Matters in Swift
🚀 Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →Functional programming brings significant benefits to Swift development, especially as applications grow in complexity. Here's why it matters:
1. Predictability and Testability
Pure functions are deterministic — given the same inputs, they always produce the same outputs. This makes unit testing trivial: you supply input, assert the expected output, and don't need to set up complex external state or mock objects:
func validateEmail(_ email: String) -> Bool {
let pattern = "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$"
return email.range(of: pattern, options: .regularExpression) != nil
}
// Testing is straightforward — no setup required
func testValidateEmail() {
assert(validateEmail("user@example.com") == true)
assert(validateEmail("invalid-email") == false)
assert(validateEmail("") == false)
}
2. Safer Concurrency
Immutable values are inherently thread-safe. Multiple threads can read the same immutable value simultaneously without race conditions, deadlocks, or the need for locks:
// Immutable data is safe to share across threads
struct UserProfile {
let id: String
let name: String
let email: String
}
let profile = UserProfile(id: "123", name: "Alice", email: "alice@example.com")
// Safe to access from any thread — no locks needed
DispatchQueue.global().async {
print(profile.name) // No risk of data races
}
DispatchQueue.global().async {
print(profile.email) // Completely safe
}
3. Cleaner, More Expressive Code
FP enables you to express complex transformations as pipelines of simple, composable operations. This eliminates temporary variables and nested loops, making code self-documenting:
// ❌ Imperative approach — verbose and error-prone
var activeUserEmails: [String] = []
for user in allUsers {
if user.isActive && !user.isDeleted {
let email = user.email.lowercased().trimmingCharacters(in: .whitespaces)
activeUserEmails.append(email)
}
}
// ✅ Functional approach — clear, concise, composable
let activeUserEmails = allUsers
.filter { $0.isActive && !$0.isDeleted }
.map { $0.email.lowercased().trimmingCharacters(in: .whitespaces) }
4. Better Composability
Small, pure functions can be combined to build complex behavior. This mirrors how SwiftUI views are composed and how Combine publishers are chained:
// Small, composable functions
func extractDomain(from email: String) -> String? {
let parts = email.split(separator: "@")
return parts.count == 2 ? String(parts[1]) : nil
}
func isCommonDomain(_ domain: String) -> Bool {
let commonDomains = Set(["gmail.com", "yahoo.com", "outlook.com", "icloud.com"])
return commonDomains.contains(domain)
}
// Compose them together
func isPersonalEmail(_ email: String) -> Bool {
guard let domain = extractDomain(from: email) else { return false }
return isCommonDomain(domain)
}
// Usage — clean and readable
isPersonalEmail("user@gmail.com") // true
isPersonalEmail("ceo@startup.io") // false
How to Use Functional Programming in Swift
Swift's standard library provides a rich set of higher-order functions that form the backbone of functional programming. Let's explore the most important ones with practical examples.
map — Transforming Elements
The map function applies a transformation to each element in a collection and returns a new collection of the transformed values. It does not modify the original collection:
let numbers = [1, 2, 3, 4, 5]
// Double each number
let doubled = numbers.map { $0 * 2 }
// doubled = [2, 4, 6, 8, 10]
// Convert to strings with formatting
let formatted = numbers.map { "Item #\($0)" }
// formatted = ["Item #1", "Item #2", "Item #3", "Item #4", "Item #5"]
// Extract a property from a struct
struct Product {
let name: String
let price: Double
}
let products = [
Product(name: "MacBook", price: 1299.99),
Product(name: "iPad", price: 799.99),
Product(name: "iPhone", price: 999.99)
]
let productNames = products.map { $0.name }
// productNames = ["MacBook", "iPad", "iPhone"]
// Apply a price discount using map
let discounted = products.map { Product(name: $0.name, price: $0.price * 0.9) }
Swift's map works on any type that conforms to the Sequence protocol, including arrays, dictionaries, sets, and even Optional types:
// Mapping over an Optional — applies transform only if value exists
let optionalNumber: Int? = 5
let squaredOptional = optionalNumber.map { $0 * $0 } // Optional(25)
let nilOptional: Int? = nil
let squaredNil = nilOptional.map { $0 * $0 } // nil
// Mapping over a dictionary
let scores = ["Alice": 85, "Bob": 92, "Charlie": 78]
let adjustedScores = scores.mapValues { $0 + 5 }
// adjustedScores = ["Alice": 90, "Bob": 97, "Charlie": 83]
filter — Selecting Elements
The filter function returns a new collection containing only elements that satisfy a given predicate (a function that returns Bool):
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// Keep only even numbers
let evenNumbers = numbers.filter { $0 % 2 == 0 }
// evenNumbers = [2, 4, 6, 8, 10]
// Keep numbers greater than 5
let greaterThanFive = numbers.filter { $0 > 5 }
// greaterThanFive = [6, 7, 8, 9, 10]
// Filter a collection of custom objects
struct Task {
let title: String
let isCompleted: Bool
let priority: Int
}
let tasks = [
Task(title: "Write report", isCompleted: false, priority: 3),
Task(title: "Fix bug", isCompleted: true, priority: 1),
Task(title: "Review PR", isCompleted: false, priority: 2),
Task(title: "Deploy app", isCompleted: true, priority: 3)
]
let pendingTasks = tasks.filter { !$0.isCompleted }
let highPriorityTasks = tasks.filter { $0.priority == 1 }
let urgentPending = tasks.filter { !$0.isCompleted && $0.priority <= 2 }
reduce — Combining Elements into a Single Value
The reduce function combines all elements of a collection into a single accumulated value. It takes an initial value and a closure that specifies how to combine each element with the running accumulation:
let numbers = [1, 2, 3, 4, 5]
// Sum all numbers
let sum = numbers.reduce(0) { $0 + $1 }
// sum = 15
// Or more concisely using the + operator
let sum2 = numbers.reduce(0, +)
// sum2 = 15
// Product of all numbers
let product = numbers.reduce(1, *)
// product = 120
// Build a comma-separated string
let csv = numbers.reduce("") { $0.isEmpty ? "\($1)" : "\($0), \($1)" }
// csv = "1, 2, 3, 4, 5"
// Count occurrences of each word in an array
let words = ["apple", "banana", "apple", "orange", "banana", "apple"]
let wordCount = words.reduce(into: [String: Int]()) { counts, word in
counts[word, default: 0] += 1
}
// wordCount = ["apple": 3, "banana": 2, "orange": 1]
// Reduce into is more efficient for accumulating complex types
let usersByRole = ["admin", "user", "admin", "guest", "user", "user"]
let roleCounts = usersByRole.reduce(into: [:]) { $0[$1, default: 0] += 1 }
// roleCounts = ["admin": 2, "user": 3, "guest": 1]
compactMap — Transform and Remove nil Values
The compactMap function combines map and filter in one pass: it applies a transformation that may return nil, then automatically filters out all nil results:
let stringNumbers = ["1", "2", "three", "4", "five", "6"]
// Convert strings to Int, dropping invalid entries
let validNumbers = stringNumbers.compactMap { Int($0) }
// validNumbers = [1, 2, 4, 6]
// Extract optional URLs from strings
let urls = ["https://apple.com", "invalid url", "https://swift.org"]
let validURLs = urls.compactMap { URL(string: $0) }
// validURLs contains two URL objects, skipping "invalid url"
// Extract non-nil email addresses from user profiles
struct User {
let name: String
let email: String?
}
let users = [
User(name: "Alice", email: "alice@example.com"),
User(name: "Bob", email: nil),
User(name: "Charlie", email: "charlie@example.com")
]
let emailAddresses = users.compactMap { $0.email }
// emailAddresses = ["alice@example.com", "charlie@example.com"]
flatMap — Flatten Nested Collections
The flatMap function transforms each element into a sequence and then concatenates all the resulting sequences into a single flat collection. For arrays, it's excellent for flattening nested arrays or expanding each element into multiple elements:
// Flatten an array of arrays
let nestedArrays = [[1, 2], [3, 4], [5, 6]]
let flattened = nestedArrays.flatMap { $0 }
// flattened = [1, 2, 3, 4, 5, 6]
// Expand each number into an array of its divisors
func divisors(of n: Int) -> [Int] {
return (1...n).filter { n % $0 == 0 }
}
let numbers = [6, 10, 15]
let allDivisors = numbers.flatMap { divisors(of: $0) }
// allDivisors = [1, 2, 3, 6, 1, 2, 5, 10, 1, 3, 5, 15]
// For Optionals, flatMap unwraps nested optionals
let nestedOptional: Int?? = Optional(Optional(42))
let unwrapped = nestedOptional.flatMap { $0 }
// unwrapped = Optional(42) — one layer removed
sorted and sort — Ordering with Predicates
Swift's sorting functions accept closures that define custom ordering logic, enabling functional-style sorting without mutating the original data:
// sorted() returns a new sorted array, leaving original intact
let scores = [85, 92, 78, 95, 88]
let ascending = scores.sorted() // [78, 85, 88, 92, 95]
let descending = scores.sorted(by: >) // [95, 92, 88, 85, 78]
// Custom sorting with a closure
struct Player {
let name: String
let points: Int
let assists: Int
}
let players = [
Player(name: "Alex", points: 22, assists: 5),
Player(name: "Jordan", points: 18, assists: 12),
Player(name: "Taylor", points: 22, assists: 8)
]
// Sort by points descending, then assists descending for ties
let ranked = players.sorted { p1, p2 in
if p1.points != p2.points {
return p1.points > p2.points
}
return p1.assists > p2.assists
}
Chaining Higher-Order Functions
The true power of functional programming emerges when you chain these operations together to form data transformation pipelines:
struct Transaction {
let amount: Double
let category: String
let date: Date
let isReconciled: Bool
}
let transactions: [Transaction] = [
Transaction(amount: 45.99, category: "Food", date: Date(), isReconciled: true),
Transaction(amount: 120.00, category: "Utilities", date: Date(), isReconciled: false),
Transaction(amount: 12.50, category: "Food", date: Date(), isReconciled: true),
Transaction(amount: 200.00, category: "Rent", date: Date(), isReconciled: true),
Transaction(amount: 35.75, category: "Food", date: Date(), isReconciled: false),
Transaction(amount: 85.00, category: "Internet", date: Date(), isReconciled: true)
]
// Calculate total reconciled food spending
let reconciledFoodTotal = transactions
.filter { $0.isReconciled }
.filter { $0.category == "Food" }
.map { $0.amount }
.reduce(0, +)
// Better: combine filters to reduce passes
let optimizedTotal = transactions
.filter { $0.isReconciled && $0.category == "Food" }
.reduce(0) { $0 + $1.amount }
// Complex pipeline: get categories with spending over $50, sorted by total
let highSpendingCategories = Dictionary(
grouping: transactions.filter { $0.isReconciled },
by: { $0.category }
)
.mapValues { $0.reduce(0) { $0 + $1.amount } }
.filter { $0.value > 50 }
.sorted { $0.value > $1.value }
.map { (category: $0.key, total: $0.value) }
// Result: [("Rent", 200.0), ("Food", 58.49), ("Internet", 85.0)]
Working with lazy Collections
Swift's lazy modifier defers computation until results are actually accessed, avoiding intermediate array allocations when chaining operations:
let hugeArray = 1...1_000_000
// Eager evaluation — creates intermediate arrays at each step
let eager = hugeArray
.filter { $0 % 2 == 0 }
.map { $0 * $0 }
.prefix(5) // All 1M elements processed!
// Lazy evaluation — only computes what's needed
let lazy_result = hugeArray
.lazy
.filter { $0 % 2 == 0 }
.map { $0 * $0 }
.prefix(5) // Only processes until 5 elements found
// Convert to array when you actually need the values
let firstFiveSquares = Array(lazy_result)
// firstFiveSquares = [4, 16, 36, 64, 100]
// Lazy chains are ideal for infinite sequences or very large collections
// where you only need a subset of the results
Function Composition and Custom Operators
You can build your own function composition tools to create reusable transformation pipelines:
// Custom composition operator
infix operator >>> : CompositionPrecedence
func >>> (lhs: @escaping (T) -> U, rhs: @escaping (U) -> V) -> (T) -> V {
return { rhs(lhs($0)) }
}
// Building blocks
func trimWhitespace(_ s: String) -> String {
return s.trimmingCharacters(in: .whitespacesAndNewlines)
}
func lowercase(_ s: String) -> String {
return s.lowercased()
}
func removeSpecialChars(_ s: String) -> String {
let allowed = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: " _-"))
return s.components(separatedBy: allowed.inverted).joined()
}
// Compose a reusable pipeline
let sanitizeString: (String) -> String = trimWhitespace >>> lowercase >>> removeSpecialChars
// Use the composed function
sanitizeString(" Hello, World! ") // "hello world"
sanitizeString(" Swift 5.9 ") // "swift 59"
// Another composition example — data processing
func parseJSON(_ data: Data) throws -> [String: Any] {
return try JSONSerialization.jsonObject(with: data) as? [String: Any] ?? [:]
}
func extractUsers(_ dict: [String: Any]) -> [[String: Any]] {
return dict["users"] as? [[String: Any]] ?? []
}
func filterActive(_ users: [[String: Any]]) -> [[String: Any]] {
return users.filter { ($0["active"] as? Bool) == true }
}
let processUserData = parseJSON >>> extractUsers >>> filterActive
Functional Error Handling with Result Type
Swift's Result type enables functional error handling without throwing exceptions, making error paths explicit and composable:
enum NetworkError: Error {
case invalidURL
case noData
case decodingFailed
}
func fetchData(from urlString: String) -> Result {
guard let url = URL(string: urlString) else {
return .failure(.invalidURL)
}
// Simulated fetch
guard let data = "response".data(using: .utf8) else {
return .failure(.noData)
}
return .success(data)
}
// Chain Result operations functionally
func decodeUser(from data: Data) -> Result {
// Simulated decoding
if let decoded = String(data: data, encoding: .utf8) {
return .success(decoded)
}
return .failure(.decodingFailed)
}
// Use flatMap on Result to chain operations
let pipeline = fetchData(from: "https://api.example.com")
.flatMap { decodeUser(from: $0) }
.map { $0.uppercased() }
// Handle the result
switch pipeline {
case .success(let user):
print("User: \(user)")
case .failure(let error):
print("Error: \(error)")
}
Functional SwiftUI Patterns
SwiftUI itself is built on functional programming principles. Views are functions of state — pure, declarative descriptions of the UI for a given state:
struct ContentView: View {
// State is immutable from the view's perspective
@State private var items: [String] = []
@State private var searchText: String = ""
var body: some View {
// View is a pure function of state — same state, same UI
VStack {
SearchBar(text: $searchText)
// Functional filtering in the view body
List(filteredItems, id: \.self) { item in
Text(item)
}
}
}
// Computed property as a pure transformation of state
var filteredItems: [String] {
if searchText.isEmpty {
return items
}
return items.filter { $0.localizedCaseInsensitiveContains(searchText) }
}
}
Best Practices for Functional Programming in Swift
1. Prefer let over var Whenever Possible
Start every declaration with let. Only change it to var if you have a proven need for mutation. This single habit eliminates entire categories of bugs:
// ✅ Default to let
let apiEndpoint = "https://api.example.com/v2"
let defaultTimeout = 30
let user = fetchCurrentUser()
// Only use var when mutation is truly necessary
var currentPage = 1 // Needs to increment
var isLoading = false // Needs to toggle
2. Keep Functions Pure and Small
Aim for functions that do one thing well. If a function exceeds 20 lines or has multiple responsibilities, break it into smaller, composable pieces:
// ❌ Monolithic, impure function
func processUserData(_ rawData: [String: Any], in context: ProcessingContext) -> [User] {
context.log("Starting processing...")
var users: [User] = []
for item in rawData["items"] as? [[String: Any]] ?? [] {
guard let name = item["name"] as? String,
let email = item["email"] as? String else { continue }
let sanitizedName = name.trimmingCharacters(in: .whitespaces)
let normalizedEmail = email.lowercased()
if !normalizedEmail.contains("@") { continue }
let user = User(name: sanitizedName, email: normalizedEmail)
users.append(user)
context.log("Processed: \(sanitizedName)")
}
context.log("Completed: \(users.count) users")
return users
}
// ✅ Decomposed into pure, composable functions
func extractItems(from dict: [String: Any]) -> [[String: Any]] {
return dict["items"] as? [[String: Any]] ?? []
}
func parseUser(from item: [String: Any]) -> User? {
guard let name = item["name"] as? String,
let email = item["email"] as? String else { return nil }
let sanitized = name.trimmingCharacters(in: .whitespaces)
let normalized = email.lowercased()
guard normalized.contains("@") else { return nil }
return User(name: sanitized, email: normalized)
}
func processUserData(_ rawData: [String: Any]) -> [User] {
return extractItems(from: rawData).compactMap(parseUser(from:))
}
// Logging is handled separately via a decorator or middleware pattern
3. Use Value Types (Structs) for Data Models
Structs in Swift are value types with automatic immutability when declared with let. They prevent unintended sharing of mutable state:
// ✅ Value type — copied on assignment, no shared state
struct Address {
let street: String
let city: String
let zipCode: String
}
let home = Address(street: "123 Main St", city: "Springfield", zipCode: "90210")
var office = home // Creates an independent copy
// office.city = "Shelbyville" would require office to be var and won't affect home
// ❌ Reference type — shared mutable state
class Person {
var name: String
var address: Address
init(name: String, address: Address) {
self.name = name
self.address = address
}
}
let alice = Person(name: "Alice", address: home)
let bob = alice // bob shares the same instance
bob.name = "Bob" // This also changes alice.name — dangerous!
4. Avoid Side Effects Inside Transformations
Functions like map, filter, and reduce should not perform side effects (logging, network calls, database writes). Use dedicated iteration with forEach for side-effect-driven operations:
// ❌ Side effect inside map — confusing and error-prone
let users = ["Alice", "Bob", "Charlie"]
let processed = users.map { name -> String in
print("Processing \(name)...") // Side effect!
Analytics.track("user_processed") // Side effect!
return name.uppercased()
}
// ✅ Clean separation — transform first, then perform side effects
let processed = users.map { $0.uppercased() } // Pure transformation
processed.forEach { print("Result: \($0)") } // Side effects are explicit
5. Leverage Type-Safe Functional Patterns
Use Swift's strong type system to make invalid states impossible. Encode constraints in types rather than in runtime checks:
// ❌ Runtime validation — errors possible at any call site
func sendEmail(to address: String, subject: String, body: String) {
guard address.contains("@") else { return } // Runtime check
// Send email...
}
// ✅ Type-safe — invalid states are impossible to represent
struct EmailAddress {
let value: String
init?(_ raw: String) {
guard raw.contains("@") else { return nil }
self.value = raw.lowercased()
}
}
func sendEmail(to address: EmailAddress, subject: String, body: String) {
// No validation needed — EmailAddress guarantees validity
// Send email to address.value...
}
// Usage
if let email = EmailAddress("user@example.com") {
sendEmail(to: email, subject: "Hello", body: "...")
}
// EmailAddress("invalid") would be nil — caught at compile/init time
6. Master the zip and sequence Utilities
Swift's standard library includes utilities that support functional patterns beyond the basic higher-order functions:
// zip combines two sequences into pairs
let names = ["Alice", "Bob", "Charlie"]
let scores = [85, 92, 78]
let paired = zip(names, scores).map { "\($0.0): \($0.1)" }
// paired = ["Alice: 85", "Bob: 92", "Charlie: 78"]
// zip is perfect for creating dictionaries from parallel arrays
let keys = ["name", "email", "role"]
let values = ["Alice", "alice@example.com", "admin"]
let dictionary = Dictionary(uniqueKeysWithValues: zip(keys, values))
// dictionary = ["name": "Alice", "email": "alice@example.com", "role": "admin"]
// sequence(first:next:) generates values lazily
let powersOfTwo = sequence(first: 1) { $0 * 2 }.prefix(10)
// Array(powersOfTwo) = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
// sequence(state:next:) for more complex stateful generation
let fibonacci = sequence(state: (0, 1)) { (state: inout (Int, Int)) -> Int? in
let next = state.0
state = (state.1, state.0 + state.1)
return next
}.prefix(10)
// Array(fibonacci) = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
7. Use Key Paths for Elegant Property Access
Swift's key paths enable functional-style property access without closures, making code more concise and readable:
struct Employee {
let name: String
let department: String
let salary: Double
}
let employees = [
Employee(name: "Alice", department: "Engineering", salary: 95000),
Employee(name: "Bob", department: "Design", salary: 82000),
Employee(name: "Charlie", department: "Engineering", salary: 105000)
]
// Using key paths instead of closures
let allNames = employees.map(\.name)
// allNames = ["Alice", "Bob", "Charlie"]
let totalSalary = employees.reduce(0, \.salary, +)
// totalSalary = 282000.0
let engineeringSalaries = employees
.filter(\.department == "Engineering")
.map(\.salary)
// engineeringSalaries = [95000.0, 105000.0]
// Key paths with sorting
let bySalary = employees.sorted(by: \.salary)
// Sorted by salary ascending
8. Handle Optionals Functionally
Treat Optional as a collection of zero or one elements. Use map, flatMap, and optional chaining to operate on optional values without explicit unwrapping:
struct User {
let name: String
let address: Address?
}
struct Address {
let street: String
let city: String
let country: String?
}
let user: User? = User(name: "Alice", address: Address(street: "123 Main", city: "NYC", country: "USA"))
// Functional optional chaining — no if-let pyramids
let userCountry = user
.flatMap { $0.address }
.flatMap { $0.country }
// userCountry = Optional("USA")
// With map for transformation
let countryMessage = user
.flatMap { $0.address }
.flatMap { $0.country }
.map { "Country: \($0)" }
?? "No country specified"
// countryMessage = "Country: USA"