O poder dos genéricos em Swift. Parte 2

Boa tarde amigos Especialmente para os alunos do curso "iOS Developer. Curso Avançado ”, preparamos uma tradução da segunda parte do artigo“ O Poder dos Genéricos em Swift ”.





Tipos relacionados, onde cláusulas, subscritos e mais ...

No artigo “O poder dos genéricos no Swift. A Parte 1 descreveu funções genéricas, tipos genéricos e restrições de tipo. Se você é iniciante, recomendo que você leia a primeira parte para entender melhor.

Ao definir um protocolo, às vezes é útil declarar um ou mais tipos relacionados como parte da definição. O tipo associado especifica o nome do stub para o tipo usado como parte do protocolo. O tipo real usado para esse tipo relacionado não será especificado até que o protocolo seja usado. Os tipos associados são declarados usando a palavra-chave associatedtype .

Podemos definir o protocolo para a pilha que criamos na primeira parte .

 protocol Stackable { associatedtype Element mutating func push(element: Element) mutating func pop() -> Element? func peek() throws -> Element func isEmpty() -> Bool func count() -> Int subscript(i: Int) -> Element { get } } 

O protocolo Stackable define a funcionalidade necessária que qualquer pilha deve fornecer.

Qualquer tipo que esteja em conformidade com o protocolo Stackable deve poder especificar o tipo de valor que ele armazena. Ele deve garantir que apenas elementos do tipo correto sejam adicionados à pilha e deve ficar claro que tipo de elementos são retornados por seu subscrito.

Vamos mudar nossa pilha de acordo com o protocolo:

 enum StackError: Error { case Empty(message: String) } protocol Stackable { associatedtype Element mutating func push(element: Element) mutating func pop() -> Element? func peek() throws -> Element func isEmpty() -> Bool func count() -> Int subscript(i: Int) -> Element { get } } public struct Stack<T>: Stackable { public typealias Element = T var array: [T] = [] init(capacity: Int) { array.reserveCapacity(capacity) } public mutating func push(element: T) { array.append(element) } public mutating func pop() -> T? { return array.popLast() } public func peek() throws -> T { guard !isEmpty(), let lastElement = array.last else { throw StackError.Empty(message: "Array is empty") } return lastElement } func isEmpty() -> Bool { return array.isEmpty } func count() -> Int { return array.count } } extension Stack: Collection { public func makeIterator() -> AnyIterator<T> { var curr = self return AnyIterator { curr.pop() } } public subscript(i: Int) -> T { return array[i] } public var startIndex: Int { return array.startIndex } public var endIndex: Int { return array.endIndex } public func index(after i: Int) -> Int { return array.index(after: i) } } extension Stack: CustomStringConvertible { public var description: String { let header = "***Stack Operations start*** " let footer = " ***Stack Operation end***" let elements = array.map{ "\($0)" }.joined(separator: "\n") return header + elements + footer } } var stack = Stack<Int>(capacity: 10) stack.push(element: 1) stack.pop() stack.push(element: 3) stack.push(element: 4) print(stack) 


Estendendo um tipo existente para indicar um tipo relacionado


Você pode estender um tipo existente para estar em conformidade com o protocolo.

 protocol Container { associatedtype Item mutating func append(_ item: Item) var count: Int { get } subscript(i: Int) -> Item { get } } extension Array: Container {} 

Incluindo restrições no tipo associado:


Você pode adicionar restrições ao tipo associado no protocolo para garantir que os tipos relacionados cumpram essas restrições.
Vamos mudar o protocolo Stackable .

 protocol Stackable { associatedtype Element: Equatable mutating func push(element: Element) mutating func pop() -> Element? func peek() throws -> Element func isEmpty() -> Bool func count() -> Int subscript(i: Int) -> Element { get } } 

Agora o tipo do elemento da pilha deve corresponder a Equatable , caso contrário, Equatable um erro em tempo de compilação.

Restrições de protocolo recursivas:


O protocolo pode fazer parte de seus próprios requisitos.

 protocol SuffixableContainer: Container { associatedtype Suffix: SuffixableContainer where Suffix.Item == Item func suffix(_ size: Int) -> Suffix } 

Suffix tem duas limitações: ele deve estar em conformidade com o protocolo SuffixableContainer (o protocolo é definido aqui) e seu tipo de Item deve corresponder ao tipo de contêiner do Item .

Há um bom exemplo na biblioteca padrão Swift na Protocol Sequence ilustrar este tópico.

Proposta sobre as limitações do protocolo recursivo: https://github.com/apple/swift-evolution/blob/master/proposals/0157-recursive-protocol-constraints.md

Extensões de tipo genérico:


Quando você estende um tipo genérico, não está descrevendo uma lista de parâmetros de tipo ao definir uma extensão. Em vez disso, uma lista de parâmetros de tipo da definição de origem está disponível no corpo da extensão e os nomes de parâmetros do tipo de fonte são usados ​​para se referir aos parâmetros de tipo da definição de origem.


 extension Stack { var topItem: Element? { return items.isEmpty ? nil : items[items.count - 1] } } 

Cláusula where genérica


Para tipos relacionados, é útil definir requisitos. O requisito é descrito pela cláusula where genérica . A cláusula where genérica permite exigir que o tipo associado esteja em conformidade com um protocolo específico ou que determinados parâmetros e tipos relacionados sejam os mesmos. A cláusula where genérica começa com a palavra-chave where , seguida por restrições para tipos relacionados ou pelo relacionamento de igualdade entre tipos e tipos relacionados. A cláusula Where genérica é escrita antes do colchete de abertura do tipo ou corpo da função.

 func allItemsMatch<C1: Container, C2: Container> (_ someContainer: C1, _ anotherContainer: C2) -> Bool where C1.Item == C2.Item, C1.Item: Equatable { } 

Extensões com condições genéricas em que


Você pode usar a cláusula where genérica como parte da extensão. O exemplo a seguir estende a estrutura geral da Stack dos exemplos anteriores, adicionando o isTop (_ :) .

 extension Stack where Element: Equatable { func isTop(_ item: Element) -> Bool { guard let topItem = items.last else { return false } return topItem == item } } 

A extensão adiciona o isTop (_ :) apenas quando itens na pilha suportam Equatable. Você também pode usar a cláusula where genérica com extensões de protocolo. Você pode adicionar vários requisitos à where separando-os com uma vírgula.

Tipos associados à cláusula Generic em que:


Você pode incluir a cláusula where genérica no tipo associado.

 protocol Container { associatedtype Item mutating func append(_ item: Item) var count: Int { get } subscript(i: Int) -> Item { get } associatedtype Iterator: IteratorProtocol where Iterator.Element == Item func makeIterator() -> Iterator } 

Para um protocolo que herda de outro protocolo, adicione uma restrição ao tipo de ligação herdada, incluindo a cláusula where genérica na declaração do protocolo. Por exemplo, o código a seguir declara um protocolo ComparableContainer que requer que o Item Comparable suporte Item Comparable :

 protocol ComparableContainer: Container where Item: Comparable { } 

Aliases de tipo genérico:


O alias de tipo pode ter parâmetros comuns. Ainda permanecerá um alias (ou seja, não introduzirá um novo tipo).

 typealias StringDictionary<Value> = Dictionary<String, Value> var d1 = StringDictionary<Int>() var d2: Dictionary<String, Int> = d1 // okay: d1 and d2 have the same type, Dictionary<String, Int> typealias DictionaryOfStrings<T : Hashable> = Dictionary<T, String> typealias IntFunction<T> = (T) -> Int typealias Vec3<T> = (T, T, T) typealias BackwardTriple<T1,T2,T3> = (T3, T2, T1) 

Nesse mecanismo, restrições adicionais aos parâmetros de tipo não podem ser usadas.
Esse código não funcionará:

 typealias ComparableArray<T where T : Comparable> = Array<T> 

Sobrescritos genéricos


Os subscritos podem usar o mecanismo genérico e incluir a cláusula where genérica. Você escreve o nome do tipo entre colchetes após o subscript e escreve a cláusula where genérica imediatamente antes da chave de abertura do corpo do subscrito.

 extension Container { subscript<Indices: Sequence>(indices: Indices) -> [Item] where Indices.Iterator.Element == Int { var result = [Item]() for index in indices { result.append(self[index]) } return result } } 

Especialização genérica


Especialização genérica significa que o compilador clona um tipo ou função genérica, como Stack < T > , para um tipo específico de parâmetro, como Int. Essa função especializada pode ser otimizada especificamente para Int, enquanto tudo o que é supérfluo será removido. O processo de substituição de parâmetros de tipo por argumentos de tipo em tempo de compilação é chamado de especialização .

Ao especializar a função genérica para esses tipos, podemos eliminar os custos de despacho virtual, chamadas em linha, quando necessário, e eliminar a sobrecarga do sistema genérico.

Sobrecarga do operador


Tipos genéricos não funcionam com operadores por padrão, para isso você precisa de um protocolo.

 func ==<T: Equatable>(lhs: Matrix<T>, rhs: Matrix<T>) -> Bool { return lhs.array == rhs.array } 


Uma coisa interessante sobre genéricos


Por que você não pode definir uma propriedade armazenada estática para um tipo genérico?

Isso exigirá um armazenamento separado de propriedades para cada especialização genérica individual (T).

Recursos para estudo aprofundado:


https://github.com/apple/swift/blob/master/docs/Generics.rst
https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md
https://developer.apple.com/videos/play/wwdc2018/406/
https://www.youtube.com/watch?v=ctS8FzqcRug
https://medium.com/@vhart/protocols-generics-and-existential-containers-wait-what-e2e698262ab1


Só isso. Vejo você no curso .

Source: https://habr.com/ru/post/pt463789/


All Articles