Когда я учился в колледже, за обедом я спорил с друзьями о языках программирования. Мы будем продолжать и говорить о Java или Python и ООП об этом, синтаксисе об этом (и под «мы» я в основном подразумеваю себя). Однако, если честно, несмотря на полученное удовольствие, я понимаю, что большинство императивных языков программирования достаточно похожи, и обсуждать их на самом деле не стоит усилий.

Конечно, вы можете спорить о шаблонах Java или проблемах многопоточности Python, но большинство языков имеют очень похожие функции (ООП, лямбда-функции и т. д.), обернутые вокруг некоторых вариаций синтаксиса C. В конце концов, язык не сильно влияет на то, как вы думаете о структуре своего кода.

В последнее время я больше изучаю язык Swift по рекомендации коллеги. Поначалу Swift казался похожим на большинство других языков, с которыми я сталкивался (особенно на Javascript). Потом я столкнулся с протоколами.

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

Сначала они могут показаться достаточно похожими на интерфейсы C++ или абстрактные классы Java. Интерфейсы, рефераты и протоколы позволяют реализовать объявленную структуру в классе, который их наследует. Однако они отличаются не столько своими реальными возможностями, сколько контекстом.

Как я научился не волноваться и полюбил расширения

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

С помощью расширений вы можете не только добавлять в протоколы больше деталей реализации, но также можете расширять реализации классов, которые уже существуют, для реализации (в Swift это называется соответствием) протокола.

Представьте, например, что у вас есть класс, реализованный в библиотеке, который действует как контейнер, который вы хотите перебирать, используя цикл for-in. Этот класс в настоящее время не имеет реализации, допускающей итерацию. В таком языке, как C++, вы, вероятно, подумали бы либо отредактировать исходный код библиотеки, либо обернуть его в свой собственный класс со встроенной итерацией. В Swift решение гораздо более элегантное: вы просто пишете расширение, которое расширяет класс для соответствия Протоколу последовательности.

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

Расширение протокола

Из того, что я сказал до сих пор, вы можете быть склонны полагать, что на самом деле расширения являются лучшей частью Swift. Где протоколы появляются, так это в том, что они добавляют к Swift, расширяясь. Протокол можно расширить, включив в него реализации, а не только объявления. Это означает, что вы можете использовать протокол не только для требования определенных атрибутов в классе, но и для обеспечения функциональности в случае, если эти требования выполняются.

В стандартной библиотеке Swift протокол Sequence фактически содержит многие стандартные функции, которые вы ожидаете от массива в javascript, такие как Map и Reduce. Это означает, что если вы соответствуете протоколу Sequence, ваш класс получает доступ к этим функциям. Это новинка с точки зрения ООП, но она становится мощной благодаря расширению протокола, который вы никогда не писали сами. Внезапно становится возможным добавлять функции в несколько стандартных библиотек или сторонних типов библиотек только с одним расширением протокола.

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

Более конкретный пример

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

Решение состоит в том, чтобы найти протокол, которому соответствуют оба типа, и расширить этот протокол. Это сделало бы так, что они оба имеют функцию при вызове, которая поддерживает невидимость, наследуемую от типа ReversedCollection. На другом языке решение могло заключаться в написании универсальной функции, поддерживающей как ReversedCollections, так и Array. Однако в этой ситуации функция будет не частью классов, а вне их. Это было бы гораздо менее элегантным решением, чем простое добавление функции к самим объектам.

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

Спасибо за чтение. Это мой первый пост на Medium, поэтому комментарии приветствуются. Загляните на мой сайт efrain.io.