Фантомные типы в Swift

от автора

Не каждый язык со статической системой типов обладает такой строгой типобезопасностью, как Swift. Это стало возможным благодаря таким особенностям Swift, как фантомные типы (phantom types), расширения универсальных типов и перечисления со связанными типами. На этой неделе мы узнаем, как использовать фантомные типы для создания типобезопасных API.

Основы

Фантомный тип — это универсальный тип, который объявляется, но никогда не используется внутри типа, в котором он объявлен. Обычно он используется как общее ограничение для создания более надежного и безопасного API. Рассмотрим простой пример.

struct Identifier<Holder> {     let value: Int }

В приведенном выше примере у нас есть структура Identifier с объявленным универсальным типом Holder. Как видите, мы не используем тип Holder внутри типа Identifier. Поэтому этот тип называют фантомным. Теперь давайте подумаем о преимуществах использования подобных типов.

struct User {     let id: Identifier<Self> }  struct Product {     let id: Identifier<Self> }  let product = Product(id: .init(value: 1)) let user = User(id: .init(value: 1))  user.id == product.id

Создадим типы User (пользователь) и Product (продукт), воспользовавшись ранее созданной структурой Identifier. Установим значение идентификатора равным 1 для новых типов user и product. Но если мы попытаемся их сравнить, компилятор Swift выдаст ошибку:

Двоичный оператор «==» не может применяться к операндам типа Identifier-User и Identifier-Product.

И это здорово, поскольку нам не нужно сравнивать идентификаторы «пользователя» и «продукта». Мы можем это сделать только случайно. Благодаря фантомному типу компилятор Swift не позволяет нам смешивать эти идентификаторы и распознает их как совершенно разные типы. Вот еще один пример, когда компилятор Swift не позволяет нам смешивать идентификаторы.

func fetch(_ product: Identifier<Product>) -> Product? {     // return product by id }  fetch(user.id)

Типобезопасность в HealthKit

Мы изучили основы фантомных типов. Теперь мы можем перейти к более сложным примерам. Я создал пару приложений для поддержания здоровья, которые используют HealthKit для хранения и запроса данных о состоянии пользователя от Apple Watch. Рассмотрим типичный пример кода, получающий данные из приложения Apple Health.

import HealthKit  let store = HKHealthStore() let bodyMass = HKQuantityType.quantityType(     forIdentifier: HKQuantityTypeIdentifier.bodyMass )! let query = HKStatisticsQuery(     quantityType: bodyMass,     quantitySamplePredicate: nil,     options: .discreteAverage ) { _, statistics, _ in     let average = statistics?.averageQuantity()     let mass = average?.doubleValue(for: .meter()) }  store.execute(query)

В приведенном выше примере мы создаем запрос для получения веса пользователя из приложения Apple Health. В обработчике завершения мы пытаемся получить среднее значение и преобразовать его в метры. Как нетрудно догадаться, преобразовать массу тела в метры невозможно, и здесь приложение вылетает. Постараемся решить эту проблему, введя фантомный тип для создания более типобезопасного API.

enum Distance {     case mile     case meter }  enum Mass {     case pound     case gram     case ounce }  struct Statistics<Unit> {     let value: Double }   extension Statistics where Unit == Mass {     func convert(to unit: Mass) -> Double {      } }  extension Statistics where Unit == Distance {     func convert(to unit: Distance) -> Double {      } }  let weight = Statistics<Mass>(value: 75) weight.convert(to: Distance.meter)

Вот возможное решение для фреймворка HealthKit, где для повышения безопасности API используется фантомный тип. Мы вводим перечисления Mass (масса) и Distance (расстояние), чтобы работать с различными единицами измерения. Как только вы попытаетесь преобразовать массу в расстояние, компилятор Swift остановит вас, отобразив сообщение об ошибке:

Невозможно преобразовать значение типа Distance в ожидаемый тип аргумента Mass.

Заключение

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


В преддверии старта курса «iOS Developer. Professional», приглашаем всех желающих на бесплатный демо-урок по теме: «Machine Learning в iOS с помощью CoreML и CreateML».

ЗАПИСАТЬСЯ НА ДЕМО-УРОК


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


Комментарии

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

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