Создание пользовательских функций запросов с key paths

от автора

Поскольку это довольно строгий статически компилируемый язык, с первого взгляда может показаться, что Swift мало чего может предложить в плане кастомизации синтаксиса, но на самом деле это далеко не так. Благодаря таким фичам, как настраиваемые и перегруженные операторы, key paths, function/result builders и т. д., у нас есть множество возможностей для настройки синтаксиса Swift под конкретные сценарии использования.

Конечно, можно определенно утверждать, что к любому виду кастомизации синтаксиса следует подходить с большой осторожностью, поскольку нестандартный синтаксис может легко стать источником путаницы, если мы не будем достаточно осторожны. Но в определенных ситуациях этот компромисс может вполне того стоить и может позволить нам создавать что-то вроде «микро-DSL», которые на самом деле могут помочь нам сделать наш код более понятным, а не наоборот.

Инвертированные логические key paths

Чтобы рассмотреть один из таких случаев, предположим, что мы работаем над приложением для управления, фильтрации и сортировки статей, которое имеет следующую модель данных (Article):

struct Article {     var title: String     var body: String     var category: Category     var isRead: Bool     ...      }

Теперь предположим, что очень распространенной задачей в нашей кодовой базе является фильтрация различных коллекций, каждая из которых содержит экземпляры указанной выше модели. Один из способов сделать это — использовать тот факт, что любой key path литерал Swift может быть автоматически преобразован в функцию, что позволяет нам использовать следующий компактный синтаксис при фильтрации по любому логическому свойству, например в данном случае isRead:

let articles: [Article] = ... let readArticles = articles.filter(\.isRead)

Это действительно здорово, однако приведенный выше синтаксис можно использовать только тогда, когда мы хотим сравнить с true — это означает, что если бы мы хотели создать аналогично отфильтрованный массив, содержащий все непрочитанные статьи, нам пришлось бы использовать вместо этого замыкание (или передавать функцию):

let unreadArticles = articles.filter { !$0.isRead }

Это, конечно, не очень большая проблема, но если описанные выше операции выполняются во многих разных местах нашей кодовой базы, тогда мы можем начать спрашивать себя: «А было бы здорово, если бы мы могли также использовать тот же красивый key path синтаксис для инвертированных логических значений? «

Здесь на помощь приходит концепция кастомизации синтаксиса. Реализуя следующую префиксную функцию, мы фактически можем создать небольшую настройку, которая позволит нам использовать key path независимо от того, сравниваем ли мы с true или false:

prefix func !<T>(keyPath: KeyPath<T, Bool>) -> (T) -> Bool {     return { !$0[keyPath: keyPath] } }

Вышеупомянутое, по сути, является перегрузкой встроенного префиксного оператора !, который позволяет применить этот оператор к любому Bool key path, чтобы превратить его в функцию, которая инвертирует (или переворачивает) его значение, что, в свою очередь, теперь позволяет нам обработать наш массив unreadArticles следующим образом:

let unreadArticles = articles.filter(!\.isRead)

Это довольно круто и не делает наш код непонятнее, учитывая, что мы используем оператор ! таким образом, который соответствует тому, как он используется по умолчанию — для отрицания логического выражения.

Сравнение на основе key paths

 Мы можем пойти еще дальше и также сделать возможным использование key paths для формирования фильтрующих запросов, которые сравнивают данное свойство с любым видом значения Equatable. Это стало бы полезным, если бы мы, например, захотели иметь возможность отфильтровать массив Equatable по каждой категории (category) статьи. Тип этого свойства, Category, в настоящее время определяется как enum, который выглядит следующим образом:

extension Article {     enum Category {         case fullLength         case quickReads         case basics         ...     } }

Точно так же, как мы ранее уже перегружали ! с key path специфичным вариантом, мы можем сделать то же самое с оператором ==, и, как и раньше, мы вернем возвращающее Bool замыкание, которое затем может быть напрямую передано в API по типу filter:

func ==<T, V: Equatable>(lhs: KeyPath<T, V>, rhs: V) -> (T) -> Bool {     return { $0[keyPath: lhs] == rhs } }

С учетом вышеизложенного теперь мы можем легко фильтровать любую коллекцию, используя сравнение на основе key path, например:

let fullLengthArticles = articles.filter(\.category == .fullLength)

Заключение

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

Как и все, о чем я пишу здесь, на Swift от Sundell, я лично использовал. Я использовал вышеупомянутую технику в нескольких проектах, в которых мне приходилось много фильтровать коллекции, и она проявила себя замечательно, но я бы не стал ее использовать, если бы только у меня не было сильной потребности в такой функциональности.

Чтобы получить более подробный и более продвинутый вариант описанной выше техники, ознакомьтесь с разделом «Предикаты в Swift» и не стесняйтесь присылать мне свои вопросы и комментарии через Twitter или по электронной почте.


Перевод статьи был подготовлен в преддверии старта курса «IOS Developer. Professional».

  • Насколько востребованы iOS-разработчики в период кризиса?

  • Какие требования к соискателям предъявляют компании-работодатели?

  • Какие вопросы задают на собеседовании, и как не допустить ошибку при ответе?

  • Какие знания и навыки необходимы, чтобы выделиться из толпы и обеспечить себе карьерный прогресс?

На все эти вопросы, в рамах бесплатного карьерного вебинара, ответит наш эксперт — Ексей Пантелеев. Также Ексей подробно расскажет о программе курса и процессе обучения. Записаться на вебинар.

ссылка на оригинал статьи https://habr.com/ru/company/otus/blog/541492/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *