Swift Generics… Проверка соответствия протоколу с соответствующим типом

#swift #generics #protocols #associated-types

#swift #общие #протоколы #связанные типы

Вопрос:

Я пытаюсь написать универсальную функцию в Swift, которая принимает любое число, Int , Float Double и т.д., Задав общий тип в <T: Numeric> . So,

 func doSomething<T: Numeric>(with foo: T, and bar: T) -> T {
  // body
}
  

Большая часть текста будет работать для любого Numeric типа, но по пути мне нужно найти остальное… это означает, что мне нужен другой подход для FloatingPoint типов в сравнении с Integer types.

Когда T является Int , это означает использование оператора modulo:

 let remainder = foo % bar
  

Однако, когда T используется Double or Float , оператор по модулю не работает, и мне нужно использовать truncatingRemainder(dividingBy:) метод:

 let remainder = foo.truncatingRemainder(dividingBy: bar)
  

Где я изо всех сил пытаюсь найти способ отсеять их. Теоретически, я должен просто быть в состоянии сделать что-то вроде этого:

 var remainder: T
if T.self as FloatingPoint { // <- This doesn't work
  remainder = foo.truncatingRemainder(dividingBy: bar)
} else {
  remainder = foo % bar
}
  

Это, конечно, приводит к этой ошибке, поскольку FloatingPoint имеет связанный тип:

 error: protocol 'FloatingPoint' can only be used as a generic constraint because it has Self or associated type requirements
  

Я понимаю, что эта ошибка … по сути, FloatingPoint является общей с все еще общим связанным типом для определения принимающего типа.

Однако я хотел бы знать наилучший способ условного запуска блоков кода select, которые применяются только к некоторому более узко определяемому протоколу, чем тот, который определен с помощью универсального параметра ( T ).

В частности, есть ли способ определить единую универсальную функцию для обработки всех Numeric типов, а затем дифференцировать по FloatingPoint сравнению Integer с типами внутри.

Комментарии:

1. Это моя первая попытка, но она не работает, потому что вы не можете расширить протоколы, чтобы они соответствовали другим протоколам : ( pastebin.com/G2EuHLvf

Ответ №1:

Здесь есть пара проблем.

  1. Numeric — неправильный протокол, если вы хотите использовать остатки.

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

  1. Swift не позволит вам расширять протокол для соответствия другому протоколу.

Существуют различные технические причины для этого ограничения, в основном связанные с трудностями, когда тип будет соответствовать протоколу несколькими способами.

Я думаю, у вас есть два разумных решения.

A. Сделайте две разные перегрузки doSomething , одну с T: FloatingPoint , а другую с T: BinaryInteger . Если между этими реализациями слишком много общего кода, вы могли бы преобразовать doSomething в несколько функций, большинство из которых были бы определены во всех Numeric .

B. Введите новый протокол RemainderArithmetic: Numeric , который описывает нужные вам операции. Затем напишите явные соответствия для всех конкретных типов, которые вы хотите использовать, например, extension Double: RemainderArithmetic и extension UInt: RemainderArithmetic .

Ни одно из этих решений не является особенно привлекательным, но я думаю, что у них есть одно ключевое преимущество: оба этих решения проясняют конкретную семантику типов, которые вы ожидаете. На самом деле вы не ожидаете типов, отличных от BinaryInteger ‘s или FloatingPoint ‘s, поэтому вам не следует принимать другие типы. Семантика остатков может быть чрезвычайно сложной, о чем свидетельствует широкий спектр поведений, описанных на странице Википедии для mod. Поэтому, вероятно, не очень естественно определять функцию одинаково для целых чисел и чисел с плавающей запятой. Если вы уверены, что это то, что вы хотите сделать, оба этих решения делают ваши предположения о том, какие типы вы ожидаете, явными.

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

Комментарии:

1. Хорошие моменты. Я также пришел к выводу, что вы не можете создать единую универсальную функцию для покрытия всех чисел… он должен быть разделен на два общих: один для SignedInteger и один для FloatingPoint . Главное — избегать повторяющихся блоков логики для каждого. Однако, я думаю, что это можно изменить, разделив общие разделы кода на служебные функции, которые могут использовать оба.

Ответ №2:

Это не похоже на хороший вариант использования для универсального. Вы заметите, что такие операторы, как , не являются универсальными в Swift. Они используют перегрузку, и вы тоже должны.

Комментарии:

1. Хороший вызов. В настоящее время у меня есть два дженерика: один для SignedInteger и один для FloatingPoint .