Фантомные типы в 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 не будет опубликован. Обязательные поля помечены *