Сила дженериков в Swift. Часть 2

Автор оригинала: Aaina jain
  • Перевод
Добрый день, друзья. Специально для студентов курса «iOS Разработчик. Продвинутый курс» мы подготовили перевод второй части статьи «Сила дженериков в Swift».





Связанные типы, условия where, сабскрипты и прочее…

В статье «Сила дженериков в Swift. Часть 1» описывались generic-функции, generic-типы и ограничения типа. Если вы новичок, я бы рекомендовала вам для лучшего понимания сначала прочитать первую часть.

При определении протокола иногда полезно объявить один или несколько связанных типов как часть определения. Связанный тип задает имя-заглушку для типа, который используется в качестве части протокола. Фактический тип, используемый для этого связанного типа не будет указан, пока протокол не будет использован. Связанные типы объявляются с помощью ключевого слова associatedtype.

Мы можем определить протокол для стека, который мы создали в первой части.

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

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

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

Давайте изменим наш стек в соответствии с протоколом:

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)


Расширение существующего типа для указания связанного типа


Вы можете расширить существующий тип, чтобы обеспечить соответствие протоколу.

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

Добавление ограничений в связанный тип:


Вы можете добавить ограничения к связанному типу в протоколе для гарантии соответствия связанных типов этим ограничениям.
Давайте изменим протокол 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 }
}

Теперь тип элемента стека должен соответствовать Equatable, иначе произойдет ошибка времени компиляции.

Рекурсивные ограничения протокола:


Протокол может являться частью собственных требований.

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

Suffix имеет два ограничения: он должен соответствовать протоколу SuffixableContainer (здесь определяется протокол), а его тип Item должен совпадать с типом Item контейнера.

В стандартной библиотеке Swift в Protocol Sequence есть хороший пример, иллюстрирующий эту тему.

Предложение об ограничениях рекурсивного протокола: https://github.com/apple/swift-evolution/blob/master/proposals/0157-recursive-protocol-constraints.md

Расширения Generic-типа:


Когда вы расширяете generic-тип, вы не описываете список параметров типа при определении расширения. Вместо этого список параметров типа из исходного определения доступен в теле расширения, а имена параметров исходного типа используются для ссылки на параметры типа из исходного определения.


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

Generic-условие where


Для связанных типов бывает полезно определить требования. Требование описывается generic-условием where. Generic-условие where позволяет вам требовать, чтобы связанный тип соответствовал определенному протоколу или чтобы определенные параметры типа и связанные типы были одинаковыми. Generic-условие where начинается с ключевого слова where, за которым следуют ограничения для связанных типов или отношения равенства между типами и связанными типами. Generic-условие where пишется прямо перед открывающей фигурной скобкой тела типа или функции.

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

Расширения с Generic-условиями where


Вы можете использовать generic-условие where как часть расширения. Приведенный ниже пример расширяет общую структуру Stack из предыдущих примеров, с помощью добавления метода isTop (_ :).

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

Расширение добавляет метод isTop (_ :) только тогда, когда элементы в стеке поддерживают Equatable. Также вы можете использовать generic-условие where с расширениями протокола. К условию where можно добавить несколько требований, разделив их запятой.

Связанные типы с Generic-условием where:


Вы можете включить generic-условие where в связанный тип.

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
}

Для протокола, который наследуется от другого протокола, вы добавляете ограничение к унаследованному связанному типу, включая generic-условие where в объявление протокола. Например, следующий код объявляет протокол ComparableContainer, который требует, чтобы Item поддерживал Comparable:

protocol ComparableContainer: Container where Item: Comparable { }

Дженерик алиасы типов:


Алиас типов может иметь общие параметры. Он все еще будет оставаться псевдонимом (то есть он не будет вводить новый тип).

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)

В данном механизме нельзя использовать дополнительные ограничения к параметрам типа.
Такой код не заработает:

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

Generic-сабскрипты


Сабскрипты могут использовать механизм дженериков и включать generic-условие where. Вы пишете имя типа в угловых скобках после subscript, и пишете generic-условие where непосредственно перед открывающей фигурной скобкой тела сабскрипта.

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

Специализация дженериков


Специализация дженериков означает, что компилятор клонирует универсальный тип или функцию, такую как Stack <T>, для конкретного типа параметра, такого как Int. Эта специализированная функция может быть затем оптимизирована специально для Int, при этом все лишнее будет удалено. Процесс замены параметров типа аргументами типа во время компиляции называется специализацией.

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

Перегрузка оператора


Generic-типы по умолчанию не работают с операторами, для этого вам нужен протокол.

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


Интересная вещь о дженериках


Почему вы не можете определить статическое хранимое свойство для универсального типа?

Это потребует отдельного хранения свойств для каждой отдельной специализации дженерика (T).

Ресурсы для углубленного изучения:


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


На этом все. До встречи на курсе.
OTUS. Онлайн-образование
762,20
Цифровые навыки от ведущих экспертов
Поделиться публикацией

Похожие публикации

Комментарии 0

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Самое читаемое