Как дженерики вписываются в общую картину
В шестидесятые годы было четыре основных компьютерных языка, которые преобладали над всеми остальными. АЛГОЛ, КОБОЛ, ФОРТРАН и 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 году. Я стараюсь публиковать хотя бы одну статью в неделю, а в идеале даже больше. Следуйте за мной в среде, чтобы оставаться на связи.
Сохраняйте спокойствие, продолжайте кодировать.