Функциональное программирование в Swift: основы и примеры

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Функциональное программирование в Swift: от замыканий до композиции

Swift изначально проектировался как мультипарадигмальный язык. Он поддерживает объектно-ориентированное, протокол-ориентированное и функциональное программирование. Функциональный подход помогает писать более предсказуемый, тестируемый и параллельно-безопасный код. В этой статье мы разберём ключевые концепции функционального программирования на Swift с практическими примерами.

1. Чистые функции и иммутабельность

Основа функционального стиля — чистые функции. Такая функция всегда возвращает один и тот же результат для одних и тех же аргументов и не имеет побочных эффектов (не меняет внешнее состояние).

// Нечистая функция — изменяет внешнюю переменную
var total = 0
func addToTotal(_ value: Int) -> Int {
    total += value
    return total
}

// Чистая функция — только вычисление
func add(_ a: Int, _ b: Int) -> Int {
    return a + b
}

Иммутабельность (неизменяемость) достигается через let и структуры с семантикой значения. Вместо изменения массива мы создаём новый:

let numbers = [1, 2, 3, 4, 5]
// Вместо var и append:
let newNumbers = numbers + [6] // [1, 2, 3, 4, 5, 6]

2. Замыкания (Closures) как строительные блоки

Замыкания в Swift — это анонимные функции, которые могут захватывать переменные из окружающего контекста. Они являются «гражданами первого класса»: их можно передавать как параметры и возвращать из функций.

// Простое замыкание
let multiply: (Int, Int) -> Int = { $0 * $1 }
let result = multiply(3, 4) // 12

// Замыкание с захватом контекста
var counter = 0
let increment = { counter += 1 }
increment() // counter = 1
increment() // counter = 2

Замыкания активно используются в методах высшего порядка, таких как sorted:

let names = ["Иван", "Анна", "Борис", "Елена"]
let sorted = names.sorted { $0.localizedStandardCompare($1) == .orderedAscending }
print(sorted) // ["Анна", "Борис", "Елена", "Иван"]

3. Методы высшего порядка: map, filter, reduce

Это «рабочая лошадка» функционального стиля в Swift. Они позволяют обрабатывать коллекции без явных циклов и мутации.

map

Преобразует каждый элемент коллекции:

let prices = [100, 200, 300]
let withTax = prices.map { $0 * 1.2 }
print(withTax) // [120.0, 240.0, 360.0]

// Реальный пример: извлечение имён из массива моделей
struct User {
    let name: String
    let age: Int
}
let users = [User(name: "Алексей", age: 30), User(name: "Мария", age: 25)]
let userNames = users.map { $0.name }
print(userNames) // ["Алексей", "Мария"]

filter

Отбирает элементы по условию:

let numbers = [1, 2, 3, 4, 5, 6]
let evenNumbers = numbers.filter { $0 % 2 == 0 }
print(evenNumbers) // [2, 4, 6]

// Фильтрация пользователей старше 18 лет
let adults = users.filter { $0.age >= 18 }

reduce

Сводит коллекцию к одному значению:

let sum = numbers.reduce(0, +) // 21
// Собрать строку из массива
let words = ["Функциональное", "программирование", "Swift"]
let sentence = words.reduce("") { $0.isEmpty ? $1 : $0 + " " + $1 }
print(sentence) // "Функциональное программирование Swift"

Комбинирование методов

Самое интересное — цепочки вызовов:

let transactions = [250, -50, 100, -200, 300, -100]
let balance = transactions
    .filter { $0 > 0 }          // только доходы
    .map { $0 * 0.87 }          // вычитаем налог 13%
    .reduce(0, +)               // суммируем
print(balance) // 565.5

4. Композиция функций и каррирование

Композиция позволяет объединять несколько функций в одну. В Swift нет встроенного оператора композиции, но его легко реализовать:

precedencegroup CompositionPrecedence {
    associativity: left
    higherThan: AssignmentPrecedence
}

infix operator >>>: CompositionPrecedence

func >>><A, B, C>(left: @escaping (A) -> B, right: @escaping (B) -> C) -> (A) -> C {
    return { right(left($0)) }
}

// Использование
let addTax = { (price: Double) in price * 1.2 }
let formatPrice = { (price: Double) in "\(price) руб." }
let displayPrice = addTax >>> formatPrice

print(displayPrice(100)) // "120.0 руб."

Каррирование — преобразование функции с несколькими аргументами в цепочку функций с одним аргументом:

// Обычная функция
func greet(_ greeting: String, to name: String) -> String {
    return "\(greeting), \(name)!"
}

// Каррированная версия
func curriedGreet(_ greeting: String) -> (String) -> String {
    return { name in "\(greeting), \(name)!" }
}

let helloGreet = curriedGreet("Привет")
print(helloGreet("Анна")) // "Привет, Анна!"
print(helloGreet("Иван")) // "Привет, Иван!"

5. Обработка опциональных значений (Optionals)

Функциональный подход отлично работает с Optional. Вместо вложенных if let используйте map и flatMap:

let optionalNumber: Int? = 5
let doubled = optionalNumber.map { $0 * 2 } // Optional(10)

// flatMap для вложенных опционалов
let stringDict: [String: String?] = ["key1": "123", "key2": nil]
let value = stringDict["key1"]?.flatMap { Int($0) }                

Блоги

Рекомендаций по книгам