La puissance des génériques dans Swift. 2e partie

Bonjour mes amis. Surtout pour les étudiants du cours "Développeur iOS. Advanced Course », nous avons préparé une traduction de la deuxième partie de l'article« The Power of Generics in Swift ».





Types connexes, clauses where, indices, etc.

Dans l'article «Le pouvoir des génériques dans Swift. Partie 1 ” décrit les fonctions génériques, les types génériques et les restrictions de type. Si vous êtes débutant, je vous recommande de lire d'abord la première partie pour une meilleure compréhension.

Lors de la définition d'un protocole, il est parfois utile de déclarer un ou plusieurs types liés dans le cadre de la définition. Le type associé spécifie le nom du stub pour le type utilisé dans le cadre du protocole. Le type réel utilisé pour ce type associé ne sera pas spécifié tant que le protocole ne sera pas utilisé. Les types associés sont déclarés à l'aide du mot clé associatedtype .

Nous pouvons définir le protocole de la pile que nous avons créée dans la première partie .

 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 } } 

Le protocole Stackable définit les fonctionnalités nécessaires que toute pile doit fournir.

Tout type conforme au protocole Stackable doit pouvoir spécifier le type de valeur qu'il stocke. Il doit garantir que seuls les éléments du type correct sont ajoutés à la pile, et il doit être clair quel type d'éléments est renvoyé par son indice.

Modifions notre pile selon le protocole:

 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) 


Extension d'un type existant pour indiquer un type associé


Vous pouvez étendre un type existant pour se conformer au protocole.

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

Ajout de contraintes au type associé:


Vous pouvez ajouter des restrictions au type associé dans le protocole pour vous assurer que les types associés respectent ces restrictions.
Stackable protocole 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 } } 

Le type de l'élément de pile doit Equatable correspondre à Equatable , sinon une erreur de compilation se produira.

Restrictions du protocole récursif:


Le protocole peut faire partie de ses propres exigences.

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

Suffix a deux limitations: il doit être conforme au protocole SuffixableContainer (le protocole est défini ici) et son type d' Item doit correspondre au type d' Item conteneur.

Il y a un bon exemple dans la bibliothèque standard Swift dans Protocol Sequence illustrer cette rubrique.

Proposition sur les limites du protocole récursif: https://github.com/apple/swift-evolution/blob/master/proposals/0157-recursive-protocol-constraints.md

Extensions de type générique:


Lorsque vous étendez un type générique, vous ne décrivez pas une liste de paramètres de type lors de la définition d'une extension. Au lieu de cela, une liste de paramètres de type de la définition source est disponible dans le corps de l'extension, et les noms de paramètres du type source sont utilisés pour faire référence aux paramètres de type de la définition source.


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

Clause Where générique


Pour les types associés, il est utile de définir des exigences. L'exigence est décrite par la clause générique where . La clause générique where vous permet d'exiger que le type associé soit conforme à un protocole spécifique ou que certains paramètres de type et les types associés soient identiques. La clause générique where commence par le mot-clé where , suivi des contraintes pour les types associés ou de la relation d'égalité entre les types et les types associés. La clause générique where est écrite juste avant l'accolade ouvrante du type ou du corps de la fonction.

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

Extensions avec conditions génériques où


Vous pouvez utiliser la clause générique where dans le cadre de l'extension. L'exemple suivant étend la structure globale de la Stack des exemples précédents en ajoutant la isTop (_ :) .

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

L'extension ajoute la isTop (_ :) uniquement lorsque les éléments de la pile prennent en charge Equatable. Vous pouvez également utiliser la clause générique where avec des extensions de protocole. Vous pouvez ajouter plusieurs exigences à la where séparant par une virgule.

Types associés à la clause Generic où:


Vous pouvez inclure la clause générique where dans le type associé.

 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 } 

Pour un protocole qui hérite d'un autre protocole, vous ajoutez une contrainte au type lié hérité, y compris la clause générique where dans la déclaration de protocole. Par exemple, le code suivant déclare un protocole ComparableContainer qui requiert que Item Comparable charge Comparable :

 protocol ComparableContainer: Container where Item: Comparable { } 

Alias ​​de type générique:


L'alias de type peut avoir des paramètres communs. Il restera toujours un alias (c'est-à-dire qu'il n'introduira pas de nouveau type).

 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) 

Dans ce mécanisme, des restrictions supplémentaires aux paramètres de type ne peuvent pas être utilisées.
Un tel code ne fonctionnera pas:

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

Exposants génériques


Les indices peuvent utiliser le mécanisme générique et inclure la clause générique where . Vous écrivez le nom du type entre crochets après l' subscript et vous écrivez la clause générique where juste avant l'accolade ouvrante du corps de l'indice.

 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 } } 

Spécialisation générique


La spécialisation générique signifie que le compilateur clone un type ou une fonction générique, tel que Stack < T > , pour un type particulier de paramètre, tel que Int. Cette fonction spécialisée peut ensuite être optimisée spécifiquement pour Int, tandis que tout ce qui est superflu sera supprimé. Le processus de remplacement des paramètres de type par des arguments de type au moment de la compilation est appelé spécialisation .

En spécialisant la fonction générique pour ces types, nous pouvons éliminer les coûts de répartition virtuelle, les appels en ligne, si nécessaire, et éliminer les frais généraux du système générique.

Surcharge de l'opérateur


Les types génériques ne fonctionnent pas avec les opérateurs par défaut, pour cela, vous avez besoin d'un protocole.

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


Une chose intéressante sur les génériques


Pourquoi ne pouvez-vous pas définir une propriété stockée statique pour un type générique?

Cela nécessitera un stockage séparé des propriétés pour chaque spécialisation générique (T) individuelle.

Ressources pour une étude approfondie:


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


C’est tout. Rendez-vous sur le parcours .

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


All Articles