Site icon AppTractor

Что произойдет, если заменить каждый цикл For в Swift на map, filter и reduce?

Каждый разработчик Swift обожает хороший цикл for. Это наш хлеб насущный. Нужно перебрать числа? Цикл for. Собрать данные? Цикл for. Изменить состояние? Да, снова цикл for.

Но я всё время слышал из «лагеря функционального программирования» шепоток о том, что map, filter и reduce могут заменить большинство циклов for — и не просто заменить их, а сделать их чище, быстрее и выразительнее.

Поэтому я задал себе простой вопрос:

А что, если я полностью откажусь от циклов for в своей кодовой базе Swift и заменю их функциональными конструкциями?

Я решил попробовать. И результаты оказались… удивительными.

Правила эксперимента

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

  1. Никаких циклов for вообще
  2. Это включает в себя for-in, forEach и даже while (жестоко, знаю)
  3. Разрешены только функции высшего порядка — map, filter, reduce, compactMap, flatMap
  4. Использую настоящую кодовую базу

Мне не нужны были просто игрушечные примеры. Я провел этот эксперимент в небольшом приложении SwiftUI, которое я использую для отслеживания привычек.

Цель была не в том, чтобы доказать, что функциональное программирование лучше, а в том, чтобы посмотреть, что произойдёт, если зайти слишком далеко.

Разминка: простые преобразования

Начнём с малого.

Возведение чисел в квадрат

Версия цикла for:

var squares = [Int]()
for num in [1, 2, 3, 4] {
    squares.append(num * num)
}

Версия map:

let squares = [1, 2, 3, 4].map { $0 * $0 }

Более чистый код. Однострочный. Без изменяемого массива. Мне уже нравится.

Фильтрация чётных чисел

Версия с циклом for:

var evens = [Int]()
for num in 1...10 {
    if num % 2 == 0 {
        evens.append(num)
    }
}

Версия filter:

let evens = (1...10).filter { $0 % 2 == 0 }

Выразительный код. Читается как английский: «назовите мне числа, где число % 2 == 0».

Суммирование значений

Версия цикла for:

var total = 0
for num in [1, 2, 3, 4] {
    total += num
}

Версия reduce:

let total = [1, 2, 3, 4].reduce(0, +)

Код короче. Но для новичков reduce(0, +) менее очевиден, чем num in ….

Пока что функциональный Swift побеждает. Но всё становится сложнее.

Где всё начало ломаться

Замена каждого цикла for приведёт к появлению… сомнительного кода.

Вложенные циклы

Рассмотрите возможность генерации всех пар чисел.

Версия с циклом for:

var pairs = [(Int, Int)]()
for i in 1...3 {
    for j in 1...3 {
        pairs.append((i, j))
    }
}

Функциональная версия:

let pairs = (1...3).flatMap { i in
    (1...3).map { j in (i, j) }
}

Работает. Умно. Но понятнее ли? Спорный вопрос.

Изменение состояния

Предположим, вы создаёте строку.

Версия цикла for:

var text = ""
for word in ["Swift", "is", "fun"] {
    text += word + " "
}

Версия reduce:

let text = ["Swift", "is", "fun"].reduce("") { $0 + $1 + " " }

Кажется… навязанным. К тому же, reduce здесь занимает O(n²) из-за конкатенации строк. Так что цикл for может быть быстрее и безопаснее.

Ранний выход

Самая большая проблема: циклы, в которых вы прерываете или продолжаете выполнение.

for num in 1...10 {
    if num == 5 { break }
    print(num)
}

Внутри map нет элегантного break. Можно повозиться с prefix(while:), но это выглядит неестественно.

Производительность: циклы против Map/Filter/Reduce

Я провёл несколько тестов.

measure {
    var sum = 0
    for i in 1...1_000_000 {
        sum += i
    }
}

Против:

measure {
    let sum = (1...1_000_000).reduce(0, +)
}

Результат: цикл for был немного быстрее (~10–15%), но оба были невероятно быстрыми.

Настоящая разница в производительности заключалась не во времени выполнения, а в ясности.

Читаемость: моё честное мнение

После недели, проведенной в режиме только функционального кода:

Главный вывод

Итак… что произойдёт, если заменить все циклы for в Swift на map, filter и reduce?

Используйте функциональные конструкции, когда они делают код понятнее. Используйте циклы for, когда это не так.

По правде говоря, Swift даёт нам оба мира. И настоящая сила заключается не в выборе одного, а в том, чтобы знать, когда использовать каждый из них.

Источник

Exit mobile version