Kekuatan generik di Swift. Bagian 2

Selamat siang teman Khusus untuk siswa kursus "Pengembang iOS. Advanced Course ", kami menyiapkan terjemahan bagian kedua dari artikel" The Power of Generics in Swift ".





Jenis terkait, tempat klausa, subskrip, dan lainnya ...

Dalam artikel "The Power of Generics in Swift. Bagian 1 ” menjelaskan fungsi generik, tipe generik, dan batasan tipe. Jika Anda seorang pemula, saya sarankan Anda membaca bagian pertama untuk pemahaman yang lebih baik.

Saat mendefinisikan protokol, terkadang berguna untuk mendeklarasikan satu atau lebih tipe terkait sebagai bagian dari definisi. Tipe terkait menentukan nama rintisan untuk tipe yang digunakan sebagai bagian dari protokol. Tipe aktual yang digunakan untuk tipe terkait ini tidak akan ditentukan hingga protokol digunakan. Jenis yang associatedtype dideklarasikan menggunakan kata kunci tipe associatedtype .

Kita dapat mendefinisikan protokol untuk stack yang kita buat di bagian pertama .

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

Protokol Stackable mendefinisikan fungsionalitas yang diperlukan yang harus disediakan oleh setiap tumpukan.

Jenis apa pun yang sesuai dengan protokol Stackable harus dapat menentukan jenis nilai yang disimpannya. Ini harus memastikan bahwa hanya elemen-elemen dengan tipe yang benar yang ditambahkan ke stack, dan harus jelas tipe elemen apa yang dikembalikan oleh subskripnya.

Mari kita ubah tumpukan kita sesuai dengan protokol:

 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) 


Memperluas jenis yang ada untuk menunjukkan jenis terkait


Anda dapat memperluas jenis yang ada untuk mematuhi protokol.

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

Menambahkan kendala ke tipe terkait:


Anda dapat menambahkan batasan pada tipe terkait dalam protokol untuk memastikan bahwa tipe terkait mematuhi batasan-batasan ini.
Mari kita ubah protokol 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 } } 

Sekarang tipe elemen stack harus cocok dengan Equatable , jika tidak, kesalahan waktu kompilasi akan terjadi.

Batasan protokol rekursif:


Protokol dapat menjadi bagian dari persyaratannya sendiri.

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

Suffix memiliki dua batasan: ia harus sesuai dengan protokol SuffixableContainer (protokolnya didefinisikan di sini), dan tipe Item -nya harus cocok dengan tipe Item dari container.

Ada contoh yang baik di perpustakaan standar Swift di Protocol Sequence menggambarkan topik ini.

Usulan tentang batasan protokol rekursif: https://github.com/apple/swift-evolution/blob/master/proposals/0157-recursive-protocol-constraints.md

Ekstensi jenis umum:


Saat Anda memperluas tipe generik, Anda tidak mendeskripsikan daftar parameter tipe saat mendefinisikan ekstensi. Sebagai gantinya, daftar parameter tipe dari definisi sumber tersedia di tubuh ekstensi, dan nama-nama parameter dari tipe sumber digunakan untuk merujuk ke parameter tipe dari definisi sumber.


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

Generik mana klausa


Untuk jenis terkait, penting untuk mendefinisikan persyaratan. Persyaratan dijelaskan oleh klausa generic where . Klausa generik di where memungkinkan Anda untuk meminta agar tipe terkait sesuai dengan protokol tertentu atau bahwa parameter tipe tertentu dan tipe terkait adalah sama. Klausa generik di where dimulai dengan kata kunci where , diikuti oleh batasan untuk tipe terkait atau hubungan kesetaraan antara tipe dan tipe terkait. Klausa Generik di where ditulis tepat sebelum penjepit pembuka dari tipe atau fungsi tubuh.

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

Ekstensi dengan kondisi Generik di mana


Anda dapat menggunakan klausa generik where sebagai bagian dari ekstensi. Contoh berikut memperluas struktur Stack keseluruhan dari contoh sebelumnya dengan menambahkan metode isTop (_ :) .

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

Ekstensi menambahkan metode isTop (_ :) hanya ketika item pada stack mendukung Equatable. Anda juga dapat menggunakan klausa generik where dengan ekstensi protokol. Anda dapat menambahkan beberapa persyaratan ke where memisahkannya dengan koma.

Jenis terkait dengan klausa Generik di mana:


Anda bisa menyertakan klausa generik di where dalam tipe terkait.

 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 } 

Untuk protokol yang mewarisi dari protokol lain, Anda menambahkan batasan untuk tipe terikat yang diwarisi, termasuk klausa generik di where dalam deklarasi protokol. Misalnya, kode berikut mendeklarasikan protokol ComparableContainer yang mengharuskan Item mendukung Comparable :

 protocol ComparableContainer: Container where Item: Comparable { } 

Alias ​​tipe umum:


Jenis alias dapat memiliki parameter umum. Itu masih akan menjadi alias (artinya, itu tidak akan memperkenalkan tipe baru).

 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) 

Dalam mekanisme ini, batasan tambahan untuk parameter tipe tidak dapat digunakan.
Kode seperti itu tidak akan berfungsi:

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

Superskrip generik


Subskrip dapat menggunakan mekanisme generik dan menyertakan klausa generic where . Anda menulis nama jenis dalam kurung sudut setelah subscript , dan menulis generik di where klausa segera sebelum kurung pembuka badan subskrip.

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

Spesialisasi generik


Spesialisasi generik berarti bahwa kompiler mengkloning tipe atau fungsi generik, seperti Stack < T > , untuk tipe parameter tertentu, seperti Int. Fungsi khusus ini kemudian dapat dioptimalkan khusus untuk Int, sementara semua yang berlebihan akan dihapus. Proses penggantian parameter tipe dengan argumen tipe pada waktu kompilasi disebut spesialisasi .

Dengan mengkhususkan fungsi generik untuk jenis ini, kita dapat menghilangkan biaya pengiriman virtual, panggilan inline, bila perlu, dan menghilangkan overhead sistem generik.

Operator Kelebihan


Tipe generik tidak berfungsi dengan operator secara default, untuk ini Anda memerlukan protokol.

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


Suatu hal yang menarik tentang obat generik


Mengapa Anda tidak bisa mendefinisikan properti tersimpan statis untuk tipe generik?

Ini akan membutuhkan penyimpanan properti yang terpisah untuk setiap spesialisasi generik (T) individu.

Sumber daya untuk studi mendalam:


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


Itu saja. Sampai jumpa di lapangan .

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


All Articles