Расширение протокола Swift с AssociatedType, ограниченным коллекцией, не может использовать индекс

Я пытаюсь написать протокол, соответствующий протоколу сбора, и у него есть связанный тип - объект и объект свойства.

protocol DDCDataSource: Collection
{
    associatedtype Object
    var object: Object {get set}
}

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

Невозможно присвоить значение типа Self.Object индекс типа Self.Object.Index

введите здесь описание изображения

extension DDCDataSource where Object: Collection
{
    typealias Index = Object.Index

    var startIndex: Object.Index {
        get {
            return object.startIndex
        }
    }

    var endIndex: Object.Index {
        get {
            return object.endIndex
        }
    }

    subscript(position: Object.Index) -> Element
    {
        return object[position]
    }

    func index(after i: Object.Index) -> Object.Index {
        return object.index(after: i)
    }
}

person Chris Wood    schedule 09.09.2017    source источник
comment
Пожалуйста, не используйте изображения, вместо этого добавьте фрагмент кода.   -  person Pranav Kasetti    schedule 09.09.2017
comment
Я добавил тег swift 3.2, потому что он специфичен для Xcode 9. В Xcode 8 у вас будет Использование необъявленного типа «Элемент».   -  person Cœur    schedule 09.09.2017
comment
extension DDCDataSource where Object: RandomAccessCollection, кажется, немного лучше.   -  person dengApro    schedule 09.09.2017
comment
@ Cœur: Нет, это приведет к сбою во время выполнения.   -  person Martin R    schedule 10.09.2017
comment
@MartinR Это нормально для сбоя по тем же причинам, что и _ = ["foo", "bar"][3] во время выполнения. Тем не менее, мы не запрещаем Swift реализовывать индексы таким образом.   -  person Cœur    schedule 10.09.2017
comment
@ Cœur: Я не понимаю, что вы имеете в виду. Я протестировал код с фактическим типом, соответствующим DDCDataSource. Изменение типа возвращаемого значения или добавление псевдонима типа (как я предложил ниже) заставляет его компилировать и запускать, как ожидалось, но с return object[position] as! Element он вылетает во время выполнения.   -  person Martin R    schedule 10.09.2017
comment
@MartinR При правильном индексе не вылетает. Я использовал subscript(test position: Object.Index) -> Element { return object[position] as! Element } и extension Array: DDCDataSource { typealias Object = Array; var object: Array { get { return self } set { } } }. Затем я проверяю и print(["a", "b"][test: 1]) выводит b, в то время как print(["a", "b"][test: 3]) выдает фатальную ошибку: индекс вне допустимого диапазона.   -  person Cœur    schedule 10.09.2017
comment
@ Cœur: Я тестировал это с struct MyDataSource: DDCDataSource { var object = [1, 2, 3] } и let mds = MyDataSource() ; print(mds[1]) - В вашем примере Object.Element и Self.Element идентичны, в общем случае это не так.   -  person Martin R    schedule 10.09.2017
comment
@MartinR Тип MyDataSource не соответствует протоколу _IndexableBase, а Тип MyDataSource не соответствует протоколу Collection. Итак, ваш код не строится. Xcode 9 beta 6, Swift 3.2.   -  person Cœur    schedule 10.09.2017
comment
@ Cœur: Это странно. Я добавил полный пример ниже, у меня он работает.   -  person Martin R    schedule 10.09.2017


Ответы (3)


Краткий ответ: измените тип возвращаемого значения метода нижнего индекса на Object.Element

subscript(position: Object.Index) -> Object.Element {
    return object[position]
}

или добавьте псевдоним типа (аналогично тому, как вы это делали для типа Index)

typealias Element = Object.Element

subscript(position: Object.Index) -> Element {
    return object[position]
}

Это заставляет код компилироваться и работать должным образом.


Пояснение. subscript метод Collection является объявлен как

subscript(position: Self.Index) -> Self.Element { get }

где Self.Index и Self.Element - связанные типы `Collection. С вашим кодом

subscript(position: Object.Index) -> Element {
    return object[position]
}

компилятор принимает Self.Index как Object.Index, но нет связи между Self.Element и Object.Element (который возвращается object[position]). Ошибка становится более очевидной, если вы добавите явное приведение:

subscript(position: Object.Index) -> Element {
    return object[position] as Element
}

Теперь компилятор жалуется

ошибка: «Self.Object.Element» нельзя преобразовать в «Self.Element»; вы хотели использовать "как!" заставить опускаться?

Правильным решением является не принудительное приведение, а чтобы компилятор знал, что Self.Element равно Object.Element, путем добавления псевдонима типа или изменения типа возвращаемого значения.

subscript(position: Object.Index) -> Object.Element {
    return object[position]
}

так что компилятор делает вывод DDCDataSource.Element как Object.Element.


Полностью автономный пример: (Swift 4, Xcode 9 beta 6)

(Обратите внимание, что вы можете опустить ключевое слово get для вычисляемых свойств, доступных только для чтения.)

protocol DDCDataSource: Collection {
    associatedtype Object
    var object: Object { get set }
}

extension DDCDataSource where Object: Collection {
    var startIndex: Object.Index {
        return object.startIndex
    }

    var endIndex: Object.Index {
        return object.endIndex
    }

    subscript(position: Object.Index) -> Object.Element {
        return object[position]
    }

    func index(after i: Object.Index) -> Object.Index {
        return object.index(after: i)
    }
}

struct MyDataSource: DDCDataSource {
    var object = [1, 2, 3]
}

let mds = MyDataSource()
print(mds[1]) // 2

for x in mds { print(x) } // 1 2 3
person Martin R    schedule 10.09.2017

Во-первых, я думаю, вам следует определить Element,

Во-вторых, вы используете object[position], «Объект соответствует коллекции», но он не относится к типам коллекции. Очевидно, это не массив.

Как сообщает Apple: массив соответствует CustomDebugStringConvertible / CustomReflectable / CustomStringConvertible / CVarArg / Decodable / Encodable / ExpressibleByArrayLiteral / MutableCollection / RandomAccessCollection / RangeReplaceableCollection

Я думаю, что extension DDCDataSource where Object: Array лучше.

И элемент в массиве должен быть Element определен. Просто чаевые.

person dengApro    schedule 09.09.2017
comment
Это протокол, цель которого - сделать его очень универсальным и позволить классам / структурам, которые его используют, определять Element. Также я хочу, чтобы Object работал со всеми типами коллекций, а не только с массивами. - person Chris Wood; 10.09.2017
comment
Да, протокол краткий. Я пытаюсь это упростить. Отчасти разделяй и властвуй. И спасибо. Я новичок в Swift 4. Element мне кажется странным. У меня есть, хотя и не так хорошо, как Мартин Р. Нет голосов против - это хорошо. - person dengApro; 10.09.2017

Попробуй это:

subscript(position:Object.Index) -> Element
    {
        var element: Element
        guard let elementObject = object[position] else {
            //handle the case of 'object' being 'nil' and exit the current scope
        }
        element = elementObject as! Element

    }
person Torongo    schedule 09.09.2017
comment
Это действительно компилируется? object[position] не является обязательным. - person Martin R; 10.09.2017
comment
Правильно, это не является обязательным - но исключая оператор защиты и просто возвращая объект [позиция] как! Элемент будет компилироваться. Что касается того, выйдет ли он из строя или нет - я еще не пробовал запускать его. - person Chris Wood; 10.09.2017