Поскольку это довольно строгий статически компилируемый язык, с первого взгляда может показаться, что 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/
Добавить комментарий