Site icon AppTractor

Пакеты параметров типа и значения в Swift с объяснением

Пакеты параметров типа и пакеты параметров значения позволяют написать общую функцию, принимающую произвольное количество аргументов разных типов. В результате выпуска SE-393, SE-398 и SE-399 вы сможете использовать эту новую возможность, начиная со Swift 5.9.

Одним из наиболее заметных мест, на которые повлияют эти изменения, будет ограничение в 10 View в SwiftUI, которое больше не существует благодаря вариативным дженерикам, ставшими возможными после этих предложений. Также вероятно, что основной код, который вы уже использовали, теперь можно переписать с использованием пакетов параметра. Это продвинутая функция в Swift, но давайте посмотрим, какие преимущества вы можете получить от ее использования в своих проектах.

Что такое пакеты параметров типа и значения?

Пакеты параметров типа (type parameter pack) и значений (value parameter pack) всегда используются вместе. Их длина совпадает, а соответствующий входной индекс равен выходному индексу. Без контекста это трудно понять, поэтому рассмотрим пример:

func eachFirst<each T: Collection>(_ item: repeat each T) -> (repeat (each T).Element?) {
    return (repeat (each item).first)
}

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

Сначала мы определили пакет параметров типа each T: Collection. Другими словами, у нас есть пакет параметров типа, состоящий из коллекций. То же самое относится и к аргументу функции repeat each T, который сообщает нам, что мы будем повторять каждую входную коллекцию.

Функция возвращает пакет параметров значения. В данном случае это пакет значения, содержащий все первые элементы входных коллекций.

В качестве примера воспользуемся методом на примере коллекции целых чисел и имен:

let numbers = [0, 1, 2]
let names = ["Antoine", "Maaike", "Sep"]
let firstValues = eachFirst(numbers, names)
print(firstValues) // Optional(0), Optional("Antoine")

Как я уже говорил, входной индекс совпадает с выходным. В качестве первого аргумента мы передаем массив чисел, а его первое значение возвращается как первый элемент в сформированном пакете значений.

Какие проблемы решают пакеты параметров

Пакеты параметров помогают нам писать многократно используемый код и избавляют нас от необходимости писать много перегрузок. До появления пакетов параметров нам пришлось бы писать множество перегрузок следующим образом:

func eachFirst<T>(
    _ item: T
) -> T?

func eachFirst<T1, T2>(
    _ item1: T1,
    _ item2: T2
) -> (T1?, T2?)

func eachFirst<T1, T2, T3>(
    _ item1: T1,
    _ item2: T2,
    _ item3: T3
) -> (T1?, T2?, T3?)

Вы можете узнать эти перегрузки в таких операторах Combine, как zip, combineLatest и merge. Эти перегрузки также послужили причиной ограничения на 10 представлений в SwiftUI, поскольку параметр body представления использует метод базового блока построения @ViewBuilder, который был определен следующим образом:

static func buildBlock<C0, C1, C2, C3, C4, C5, C6, C7, C8, C9>(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6, _ c7: C7, _ c8: C8, _ c9: C9) -> TupleView<(C0, C1, C2, C3, C4, C5, C6, C7, C8, C9)> where C0: View, C1: View, C2: View, C3: View, C4: View, C5: View, C6: View, C7: View, C8: View, C9: View {
    return .init((c0, c1, c2, c3, c4, c5, c6, c7, c8, c9))
}

Приведенный выше пример является завершающим методом перегрузки всех перегрузок блока сборки:

Пакеты параметров типа и значения устраняют необходимость во многих перегрузках

Начиная со Swift 5.9, этот метод переписывается с использованием пакетов параметров типа и значения:

static func buildBlock<each Content>(_ content: repeat each Content) -> TupleView<(repeat each Content)> where repeat each Content : View

Почему я не могу использовать массивы?

Одним из первых вопросов, который у меня возник, был вопрос о том, нельзя ли просто использовать массивы. Однако пример, которым я поделился ранее, был бы невозможен без стирания типов.

Чтобы продемонстрировать это, я переписал предыдущий пример, используя только дженерики:

func eachFirst<T: Collection>(collections: [T]) -> [T.Element?] {
    collections.map(\.first)
}

Если мы передадим в качестве аргументов те же числа и имена, то столкнемся со следующей ошибкой:

Без пакетов параметров типа и значения использование вариативных дженериков невозможно

Поскольку в качестве входных параметров мы используем массивы int и string, компилятор не может выдать результирующее значение того же типа.

Требование минимальной длины аргумента

Методы, которые вы пишете с использованием пакетов параметров типа и значения, скорее всего, требуют наличия хотя бы одного аргумента, чтобы быть полезными. Например, наш метод eachFirst бесполезен, если мы не передаем никакого значения, и даже выдает предупреждение:

let firstValues = eachFirst()
// Warning: Constant 'firstValues' inferred to have type '()', which may be unexpected

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

func eachFirst<FirstT: Collection, each T: Collection>(_ firstItem: FirstT, _ item: repeat each T) -> (FirstT.Element?, repeat (each T).Element?) {
    return (firstItem.first, repeat (each item).first)
}

В конечном результате мы можем передать один или несколько массивов:

let numbers = [0, 1, 2]
let names = ["Antoine", "Maaike", "Sep"]
let booleans = [true, false, true]
let doubles = [3.3, 4.1, 5.6]

let firstValues = eachFirst(numbers, names, booleans, doubles)
print(firstValues) // (Optional(0), Optional("Antoine"), Optional(true), Optional(3.3))

 Заключение

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

Спасибо!

Источник

Exit mobile version