Подтвердить что ты не робот

Почему count возвращает разные типы для Collection vs. Array?

Когда я расширяю Collection тип count - IndexDistance.

Когда я расширяю тип Array count имеет тип Int

Почему существует такое различие? Это недавнее изменение, или это всегда было так?

Я прочитал этот ответ, но не мог многое подобрать.

Единственное, что я считал связанным, но не понимал:

Другим преимуществом является то, что этот [IndexDistance] также корректно работает с срезами массива (где индекс первого элемента необязательно равен нулю

Не уверен, что это значит.

Причина, по которой я спрашиваю, заключается в том, почему код вызывает ошибку в коллекции, но не делает этого в Array... хотя оба count в конечном счете являются Int.

extension Collection where Element: Comparable{
    func whatever(){
        for index in 0...count{ //  binary operator '...' cannot be applied to operands of type 'Int' and 'Self.IndexDistance'

        }
    }
}

extension Array where Element: Comparable{
    func whatever(){
        for index in 0...count{ // NO ERROR
        }
    }
}

РЕДАКТИРОВАТЬ:

Основываясь на комментариях Мартина и других, я добавил дополнительный вопрос. Вероятно, это основная причина моего вопроса...

Означает ли это, что внутри типа Collection IndexDistance не определяется Int. В основном в целом на уровне "Протокол" связанные типы не определены... Ожидает ли конкретный тип этого? Это правильно?

При этом есть ли какой-либо значимый вариант использования для доступа к count на уровне "Протокол"? Я имею в виду, что вы не можете сравнить его с любым Int так что это кажется бесполезным.

4b9b3361

Ответ 1

От ассоциированных типов на языке Swift Programming (выделено мной):

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

В Swift 3/4.0 протокол Collection определяет пять связанных типов (из Whats in Collection?):

protocol Collection: Indexable, Sequence {
    associatedtype Iterator: IteratorProtocol = IndexingIterator<Self>
    associatedtype SubSequence: IndexableBase, Sequence = Slice<Self>
    associatedtype Index: Comparable // declared in IndexableBase
    associatedtype IndexDistance: SignedInteger = Int
    associatedtype Indices: IndexableBase, Sequence = DefaultIndices<Self>
    ...
}

Вот

    associatedtype IndexDistance: SignedInteger = Int

является ассоциированным объявлением типа с ограничением типа (: SignedInteger) и значением по умолчанию (= Int),

Если тип T принимает протокол и не определяет T.IndexDistance тогда T.IndexDistance становится псевдонимом типа для Int. Это касается многих стандартных типов коллекций (таких как Array или String), но не для всех. Например

public struct AnyCollection<Element> : Collection

из стандартной библиотеки Swift

    public typealias IndexDistance = IntMax

которые вы можете проверить с помощью

let ac = AnyCollection([1, 2, 3])
let cnt = ac.count
print(type(of: cnt)) // Int64

Вы также можете определить свой собственный тип коллекции с интервалом индекса non- Int если хотите:

struct MyCollection : Collection {

    typealias IndexDistance = Int16
    var startIndex: Int { return  0 }
    var endIndex: Int { return  3 }

    subscript(position: Int) -> String {
        return "\(position)"
    }

    func index(after i: Int) -> Int {
        return i + 1
    }
}

Поэтому, если вы расширяете конкретный тип Array тогда count является Int:

extension Array {
    func whatever() {
        let cnt = count // type is 'Int'
    }
}

Но в методе расширения протокола

extension Collection {
    func whatever() {
        let cnt = count // some 'SignedInteger'
    }
}

все, что вы знаете, это то, что тип cnt - это некоторый тип, использующий протокол SignedInteger, но это не обязательно Int. Конечно, по-прежнему можно работать со счетом. На самом деле ошибка компилятора в

    for index in 0...count { //  binary operator '...' cannot be applied to operands of type 'Int' and 'Self.IndexDistance'

вводит в заблуждение. Целочисленный литерал 0 можно было бы вывести как Collection.IndexDistance из контекста (поскольку SignedInteger соответствует ExpressibleByIntegerLiteral). Но ряд SignedInteger не является Sequence, и поэтому он не компилируется.

Так что это сработает, например:

extension Collection {
    func whatever() {
        for i in stride(from: 0, to: count, by: 1) {
            // ...
        }
    }
}

Начиная с Swift 4.1, IndexDistance больше не используется, а расстояние между индексами коллекции теперь всегда выражается как Int, см.

В частности, возвращаемый тип count - Int. Существует псевдоним типа

typealias IndexDistance = Int

для компиляции более старого кода, но это считается устаревшим и будет удалено в будущей версии Swift.

Ответ 2

Не совсем ответ, но, будучи OP, я думаю, что все это было жизненно необходимым условием для моего понимания. Я этого не знал:

  • Вы можете ограничить associatedtype тип протокола
  • Вы можете указать тип associatedtype типа по умолчанию
  • Соответствие протокола associatedtype может быть сделано путем использования typealias.
  • Соответствие associatedtype протоколам может быть выполнено и другими способами, но, тем не менее, по умолчанию.
  • По умолчанию тип associatedType типа по умолчанию не запускается "на уровне протокола", т.е. Он ограничивается только ограниченным типом. Однако, как только класс/структура принимает его... тогда и только тогда используется тип по умолчанию. Более подробную информацию см. В ответе Мартина выше и в документах Apple по связанному типу
  • Существует третий способ соответствия протокола associatedtype. См. Ссылку, указанную в конце. В принципе, вы можете соответствовать, неявно определяя associatedtype тип
  • Возможно, наиболее распространенным способом является согласование протокола со связанным типом с помощью общего ограничения. См. SomeClass9

Три разных протокола

// associatedtype isn't constrained
protocol NotConstrained{
    associatedtype IndexDistance
}

// associatedtype is constrained
protocol Constrained{
    associatedtype IndexDistance: SignedInteger
}

// associatedtype is constrained and defaulted
protocol ConstrainedAndDefaulted{
    associatedtype IndexDistance: SignedInteger = Int
}

Соответствие протоколам

// All Good
class someClass1: NotConstrained{
    typealias IndexDistance = Int
}

// All Good
class someClass2: NotConstrained{
    typealias IndexDistance = String // It works with String as well, since it wasn't constrained
}

// Not Good
class SomeClass3: NotConstrained{
    // error: type 'SomeClass3' does not conform to protocol 'NotConstrained'
    // doesn't work because we MUST have a typealias
}

// All Good
class SomeClass4: Constrained{
    typealias IndexDistance = Int16
}

// Not Good
class SomeClass5: Constrained{
    typealias IndexDistance = String
    // error: type 'SomeClass5' does not conform to protocol 'Constrained'
    // Obviously! Because String isn't of type 'SignedIngeter'
}

// Not Good
class SomeClass6: Constrained{
    // error: type 'SomeClass6' does not conform to protocol 'Constrained'        
}

// All Good
class SomeClass7: ConstrainedAndDefaulted{
    // NO ERROR, because the associatedtype has already defaulted
}

// All Good
class SomeClass8: ConstrainedAndDefaulted{
    typealias IndexDistance = Int64 // We changed the default from 'Int' to 'Int64'
    // Which is ok because 'Int64' is of type 'SignedInteger'
}

class SomeClass9<T> : NotConstrained {
    typealias IndexDistance = T
}

Если вы можете понять, почему class SomeClass8 работает без ошибок, тогда вы получите свой ответ!


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