Как KeyPaths улучшают карту, compactMap, flatMap и фильтр

При работе с коллекциями, такими как массивы или наборы, мы можем использовать так называемые функции высшего порядка, такие как map, для преобразования каждого элемента коллекции или filter, чтобы получить подмножество элементов коллекции.

Функция высшего порядка - это функция, которая принимает или возвращает другие функции. Если вы новичок в них, сайт Use Your Loaf дает отличный обзор.

В этой статье мы рассмотрим новый способ их использования с KeyPaths Swift.

Сначала мы исследуем KeyPath, чтобы увидеть, что это такое и как их можно использовать. Позже мы рассмотрим новые реализации функций map, compactMap, flatMap и filter, которые используют KeyPaths, что позволяет нам сочетать мощь функций высшего порядка с удобным использованием KeyPaths.

Что такое KeyPaths?

Короче говоря, KeyPath - это функция, которая обеспечивает доступ к свойству класса или структуры с использованием специальной записи.

Давайте посмотрим на небольшой пример:

// 1— Здесь мы определяем небольшую структуру с именем User только с одним свойством name и создаем нового пользователя с именем Max.

// 2 - Обычный способ получить доступ к значению свойства name - использовать .name.

// 3— Но мы также можем использовать KeyPath, который состоит из обратной косой черты, за которой следует тип и имя свойства, к которому мы хотим получить доступ, например \User.name.

// 4. Теперь мы можем получить доступ к значению name, используя этот KeyPath. Поскольку компилятор знает, что user относится к типу User, мы можем использовать еще более короткую форму и опустить имя структуры или класса, например в этом примере \.name вместо \User.name. Мы можем использовать KeyPath как для чтения, так и для записи значения.

Итак, теперь, когда мы узнали, что такое KeyPath и как его можно использовать, давайте посмотрим, как объединить их с функциями высшего порядка!

Использование KeyPaths на карте

В следующих примерах мы будем использовать массив из User объектов, но мы будем использовать структуру, немного отличающуюся от приведенной выше:

// 1 - На этот раз у пользователя есть имя, псевдоним, список других пользователей, за которыми они следят, и он может быть в сети или офлайн. Эти свойства охватывают все случаи использования наших новых методов: необязательная строка для compactMap, массив, который будет использоваться для flatMap, и логическое значение для filter.

// 2— Мы создаем массив из двух примеров пользователей, Джеффа и Тома.

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

// 1 - Чтобы наш новый map можно было использовать так же, как и стандартный map, мы добавим его как расширение к Sequence. Этот протокол определяет различные методы для списков элементов, таких как forEach, prefix и, конечно же, map, compactMap, flatMap и filter.

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

// 2— Сигнатура функции может сначала показаться немного запутанной, поскольку мы используем два параметра универсального типа, но это довольно легко понять, если мы рассмотрим ее подробно.

Element обозначает тип элемента в последовательности, а также корневой тип KeyPath. Другой параметр универсального типа T представляет тип, в который map преобразует эти элементы.

Например, если мы хотим преобразовать массив пользователей в массив строк, извлекая их имена, Element будет User, а T будет String. Параметр keyPath - это KeyPath, который обращается к свойству типа T в классе или структуре типа Element, в примере это будет \User.name.

// 3— Теперь мы можем реализовать эту новую версию map, используя стандартный map и применяя заданный KeyPath к каждому элементу коллекции.

// 4— Чтобы использовать наш новый map, теперь мы можем передать ему KeyPath. Здесь мы преобразуем список пользователей в список строк, содержащих их имена. По сравнению с тем, как мы использовали бы стандартный map, наша новая версия выглядит чище и удобнее для чтения.

Использование KeyPaths в compactMap и flatMap

Есть два других вида map, определенных протоколом Sequence: compactMap и flatMap.

Оба преобразуют последовательность элементов в новую, но первый отбрасывает любые нулевые значения, полученные в результате преобразования, а второй выравнивает вложенные массивы.

Для получения дополнительной информации вы можете прочитать это сравнение на SwiftLee.

Затем мы также можем воссоздать compactMap и flatMap для использования KeyPaths:

// 1 - Опять же, мы расширяем протокол Sequence, чтобы их можно было использовать с массивами, словарями и наборами.

// 2— Сигнатура compactMap выглядит почти идентично предыдущей сигнатуре map, единственное отличие - это общий тип значения параметра keyPath. Это больше не T, а T?, что позволяет использовать нулевые значения в результате использования заданного KeyPath.

// 3— Как и в случае с map, мы используем стандартный compactMap и передаем KeyPath. Любые нулевые значения, полученные из KeyPath, будут удалены compactMap, в результате чего будет получен новый массив, содержащий только ненулевые значения.

// 4— Мы можем реализовать flatMap аналогичным образом. Еще раз, различие в подписи заключается в типе значения данного KeyPath, который на этот раз равен [T]. Это позволяет нам получать доступ к массивам внутри структур или классов.

// 5— Таким же образом, как мы реализовали map и compactMap, мы можем использовать стандартный flatMap в качестве основы и применить KeyPath внутри замыкания.

// 6— При сравнении этих двух версий, использующих KeyPaths, со стандартными функциями, опять же, это выглядит намного лучше.

Использование KeyPaths в filter

Наконец, давайте посмотрим, как заставить filter работать с KeyPaths:

// 1 - Поскольку filter ожидает функцию, которая возвращает логическое значение, нам нужно ограничить KeyPath, чтобы он возвращал только true или false. Таким образом, нам не нужен дополнительный параметр универсального типа T, который нам нужен в предыдущих примерах.

// 2 - Как и выше, мы начинаем со стандартного filter и используем заданный KeyPath, чтобы определить, должен ли текущий элемент быть включен в новую последовательность или нет.

// 3— Это позволяет нам просто передать KeyPath логическому значению в filter и, таким образом, устраняет необходимость доступа к текущему элементу через $0.

// 4— Но самая большая разница видна при объединении в цепочку нескольких функций высшего порядка. Например. Благодаря использованию наших новых версий комбинация filter, flatMap и map для получения имен всех пользователей, за которыми следят текущие онлайн-пользователи, является короткой, простой для понимания и легкой для написания.

Заключение

KeyPaths - это отличная функция в Swift для доступа к свойствам структур или классов.

Написав наши собственные map, compactMap, flatMap и filter, мы можем объединить простое использование KeyPaths с мощью функций высшего порядка, чтобы уменьшить шум в нашем коде и сделать его лучше.

Особенно при объединении нескольких функций высшего порядка, например. filter, затем flatMap и, наконец, map в последнем примере, KeyPaths может значительно улучшить читаемость нашего кода.

Если вам понравился этот подход, есть еще много функций высшего порядка в последовательностях, которые можно воссоздать для использования KeyPaths, например first(where:), drop(while:), contains(where:) или sorted(by:).

Ресурсы