Как дженерики вписываются в общую картину

В шестидесятые годы было четыре основных компьютерных языка, которые преобладали над всеми остальными. АЛГОЛ, КОБОЛ, ФОРТРАН и LISP. С FORTRAN и ALGOL лидируют с приведением типов. Хотя я думаю, что именно сын ALGOL, PASCAL предпринял самые решительные шаги за прошедшие десятилетия, и ALGOL отошел в сторону. PASCAL был языком, который использовался для разработки MacOS в то время, и я подозреваю, что он сильно повлиял на дизайн OBJECTIVE C, а также SWIFT.

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

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

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

С чего начать. У нас есть массив чисел. Давайте сначала упростим задачу, сказав, что все они принадлежат к одному типу. Так что, возможно, массив Float, Double или Int16. И вам нужно преобразовать их в что-то еще, например, Double и / или Integer. Вам не нужно беспокоиться о потере точности или ошибках округления, просто измените значения.

Swift compactMap - идеальный выбор для этой работы, и эта процедура всего за 3 строки кода выполняет свою работу.

let aindex = [4,3,2,1]
let converted:[Double] = convertMap(qindexIn: aindex)
func convertMap(qindexIn qindexOut:[Int]) -> [Double] {
  return(qindexOut.map({ Double($0) }))
}

Выглядит неплохо, но подождите ... вы дважды проверяете список типов чисел в Swift. У вас есть Double, Float и Int, внутри которых для последних двух вы можете выбрать между ними 8, 16, 32, 64 и 80-битную точность. Используя только что созданную функцию, вы получите много избыточного кода. Должен быть способ получше, нет, он называется дженериками.

let aindex = [4,3,2,1]
let converted:[Double] = convertMap2(qindexIn: aindex)
func convertMap2<T: BinaryInteger>(qindexIn qindexOut:[T]) -> [Double] {
  return(qindexOut.map({ Double($0) }))
}

И вот первый проход. Здесь универсальные шаблоны - это протокол, называемый BinaryInteger. Протокол, о котором вы можете прочитать на страницах документации Swift здесь. T - это не что иное, как соглашение, вы могли бы использовать V или U или любую отдельную букву. Обратите внимание, что он появляется дважды, используйте его только один раз, и компилятор запутается и пожалуется.

Итак, что это за покрытие, теперь вы можете изменить любое целое число, так что это целое число с точностью от Int8 до Int64 бит на Double с одной строкой кода. Хорошее начало - А как же наоборот. Это сработает.

let bindex = [Float(1.0),Float(2.0),Float(3.0),Float(4.0)]
let converted3:[Int] = convertMap3(qindexIn: bindex)
func convertMap3<T: BinaryFloatingPoint>(qindexIn qindexOut:[T]) -> [Int] {
  return(qindexOut.map({ Int($0) }))
}

Теперь мы снова преобразуем массив Doubles или Floats в Int любой точности. Но подождите, мы можем лучше. Эта маршрутизация использует входящий тип, чтобы определить, каким должен быть исходящий тип.

let aindex = [4,3,2,1]
let cindex = [Int8(1),Int8(2),Int8(3),Int8(4)]
let converted4:[Double] = convertMap4(qindexIn: aindex)
let converted6:[Float] = convertMap4(qindexIn: cindex)
func convertMap4<U:BinaryInteger,T:BinaryFloatingPoint>(qindexIn qindexOut:[U]) -> [T] {
  return(qindexOut.compactMap({ T($0) }))
}

Поэтому я беру массив Int в «aindex» и преобразую его в массив Double [потому что это тип преобразованного4 массива]. Или массив Int8 в «cindex» и приведите их к массиву Floats [тип преобразованного массива6]. И мы можем использовать этот метод, чтобы взять любой Float / Double и привести его к любому Int.

let bindex = [Float80(1.0),Float80(2.0),Float80(3.0),Float80(4.0)]
let converted80:[Int64] = convertMap45(qindexIn: bindex)
func convertMap45<U:BinaryFloatingPoint,T:BinaryInteger>(qindexIn qindexOut:[U]) -> [T] {
  return(qindexOut.compactMap({ T($0) }))
}

Итак, теперь у нас есть одна процедура, которая может преобразовывать любые Double / Float в любую комбинацию в Int и одна процедура для преобразования между любой комбинацией Int, которая может преобразовывать в Double / Float. Но ждать. Может даже лучше.

let converted4:[Double] = convertMap5(qindexIn: aindex)
let converted6:[Float] = convertMap5(qindexIn: cindex)
let converted8:[Int8] = convertMap5(qindexIn: cindex)
let converted16:[Int16] = convertMap5(qindexIn: aindex)
func convertMap5<U:BinaryInteger,T:Numeric>(qindexIn qindexOut:[U]) -> [T] {
  return(qindexOut.compactMap({ T(exactly: $0) }))
}

Это преобразует любой тип целого числа в Float, Double или другой тип Integer. Очевидно, вы можете заменить BinaryInteger на BinaryFloatingPoint. Итак, используя дженерики, мы почти в нирване. Но будьте осторожны, потому что здесь вы не можете изменить BinaryInteger на Numeric, см. Документацию. Это не удастся.

Теперь я уверен, что вам интересно, что случилось со строками и символами здесь, они отсутствуют. Итак, вот бонусная процедура, которая преобразует все в строку.

let aindex = [4,3,2,1]
let cindex = [Int8(1),Int8(2),Int8(3),Int8(4)]
let bindex = [Float(1.0),Float(2.0),Float(3.0),Float(4.0)]
let converted4:[String] = convertMap28(qindexIn: aindex)
let converted8:[String] = convertMap28(qindexIn: cindex)
let converted16:[String] = convertMap28(qindexIn: bindex)
func convertMap28<T>(qindexIn qindexOut:[T]) -> [String] {
  return(qindexOut.compactMap{("\($0)")})
}

Эта процедура даже преобразует массив любого типа, поэтому массив выглядит следующим образом.

На этом ноте я закрываю эту статью. Я надеюсь, что вы узнали столько же, сколько читал я, и действительно нашли что-то полезное, что вы можете здесь использовать. Эти дженерики - это гораздо больше, и я думаю, что вернусь к ним в 2021 году. Я стараюсь публиковать хотя бы одну статью в неделю, а в идеале даже больше. Следуйте за мной в среде, чтобы оставаться на связи.

Сохраняйте спокойствие, продолжайте кодировать.