Как 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:)
.