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

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

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

Начать курс

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

Swift изначально проектировался как мультипарадигмальный язык, что делает его идеальной платформой для применения продвинутых техник функционального программирования. В этой статье мы рассмотрим концепции, которые выходят за рамки базовых замыканий и map/filter/reduce — речь пойдет о функторах, монадах, композиции функций, каррировании и мемоизации. Эти инструменты помогут вам писать более предсказуемый, тестируемый и выразительный код для iOS и macOS приложений.

Функторы и монады: работа с контекстами

Функтор — это тип, который реализует функцию map, позволяя применить трансформацию к значению внутри контекста. В Swift классическими примерами функторов являются Optional и Array.

// Функтор Optional
let value: Int? = 42
let result = value.map { $0 * 2 } // Optional(84)

// Функтор Array
let numbers = [1, 2, 3]
let doubled = numbers.map { $0 * 2 } // [2, 4, 6]

Монада — это функтор с дополнительной операцией flatMap (или >>= в Haskell). Она позволяет "разворачивать" вложенные контексты. Swift поддерживает монады через протокол Monad неявно, но ключевые типы (Optional, Result, Async) реализуют эту логику.

// Монада Optional: цепочка операций с возможным nil
func parseNumber(_ string: String) -> Int? { return Int(string) }
func half(_ number: Int) -> Int? { return number % 2 == 0 ? number / 2 : nil }

let input = "42"
let result = input.flatMap(parseNumber).flatMap(half) // Optional(21)

// Монада Result для обработки ошибок
enum AppError: Error { case invalidInput }
func safeDivide(_ a: Int, _ b: Int) -> Result<Int, AppError> {
    guard b != 0 else { return .failure(.invalidInput) }
    return .success(a / b)
}

let divisionResult = safeDivide(10, 2).flatMap { safeDivide($0, 1) } // .success(5)

Продвинутое применение: создание собственного монадического типа для работы с асинхронными операциями.

struct Async<T> {
    let run: (@escaping (T) -> Void) -> Void
    
    func map<U>(transform: @escaping (T) -> U) -> Async<U> {
        return Async<U> { callback in
            self.run { value in callback(transform(value)) }
        }
    }
    
    func flatMap<U>(transform: @escaping (T) -> Async<U>) -> Async<U> {
        return Async<U> { callback in
            self.run { value in transform(value).run(callback) }
        }
    }
}

Композиция функций: строим pipeline

Композиция функций — это создание новой функции путем последовательного применения нескольких функций. В Swift это можно реализовать через оператор или замыкания.

// Оператор композиции
precedencegroup CompositionPrecedence {
    associativity: left
    higherThan: FunctionArrowPrecedence
}

infix operator >>> : CompositionPrecedence

func >>>(_ f: @escaping (A) -> B, _ g: @escaping (B) -> C) -> (A) -> C {
    return { x in g(f(x)) }
}

// Пример: валидация и форматирование
let trimWhitespace: (String) -> String = { $0.trimmingCharacters(in: .whitespaces) }
let capitalize: (String) -> String = { $0.capitalized }
let addExclamation: (String) -> String = { $0 + "!" }

let processString = trimWhitespace >>> capitalize >>> addExclamation
print(processString("  hello world  ")) // "Hello World!"

Композиция особенно полезна при работе с цепочками трансформаций данных, например, при обработке сетевых ответов.

// Продвинутая композиция с обработкой ошибок
typealias ResultFunction = (T) -> Result<U, Error>

func compose(_ f: @escaping ResultFunction<T, U>, _ g: @escaping ResultFunction<U, V>) -> ResultFunction<T, V> {
    return { input in
        f(input).flatMap(g)
    }
}

let fetchUser: (Int) -> Result<User, Error> = { id in /* ... */ }
let validateUser: (User) -> Result<User, Error> = { user in /* ... */ }
let processUser = compose(fetchUser, validateUser)

Каррирование: частичное применение функций

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

// Каррированная функция
func curriedAdd(_ a: Int) -> (Int) -> Int {
    return { b in a + b }
}

let addFive = curriedAdd(5)
print(addFive(3)) // 8

// Каррирование для конфигурации
func configureLogger(level: LogLevel) -> (String) -> Void {
    return { message in
        print("[\(level.rawValue)] \(message)")
    }
}

let errorLogger = configureLogger(level: .error)
let infoLogger = configureLogger(level: .info)

errorLogger("File not found") // [ERROR] File not found
infoLogger("User logged in")  // [INFO] User logged in

Продвинутый пример: каррирование для создания гибких UI-компонентов.

// Каррирование для стилизации UI
func styleLabel(font: UIFont, color: UIColor, alignment: NSTextAlignment) -> (UILabel) -> Void {
    return { label in
        label.font = font
        label.textColor = color
        label.textAlignment = alignment
    }
}

let titleStyle = styleLabel(font: .boldSystemFont(ofSize: 18), color: .black, alignment: .center)
let subtitleStyle = styleLabel(font: .systemFont(ofSize: 14), color: .gray, alignment: .left)

let titleLabel = UILabel()
titleStyle(titleLabel) // Применяем стиль

Мемоизация: оптимизация вычислений

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

// Общая функция мемоизации
func memoize<In: Hashable, Out>(_ function: @escaping (In) -> Out) -> (In) -> Out {
    var cache = [In: Out]()
    let queue = DispatchQueue(label: "memoize.queue", attributes: .concurrent)
    
    return { input in                

Блоги

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