3

I'm trying to write a protocol that conforms to the Collection Protocol, and it has an associatedType - Object and a property object.

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

I want to add some default functionality for the case where Object also conforms to the Collection protocol, namely just directly return Object's implementation of these required Collection properties and functions. It seems like it all works except for Collection's requirement for a subscript.

Cannot subscript a value of type 'Self.Object' with an index of type 'Self.Object.Index'

enter image description here

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)
    }
}
Cœur
  • 37,241
  • 25
  • 195
  • 267
Chris Wood
  • 306
  • 3
  • 13
  • 1
    Please don't use images, add a code snippet instead. – Pranav Kasetti Sep 09 '17 at 10:24
  • I added the tag swift 3.2 because it's specific to Xcode 9. On Xcode 8 you'd have _"Use of undeclared type 'Element'"_. – Cœur Sep 09 '17 at 10:53
  • `extension DDCDataSource where Object: RandomAccessCollection`, feels like a little better. – dengApro Sep 09 '17 at 11:16
  • 1
    `return object[position] as! Element` – Cœur Sep 09 '17 at 11:22
  • @Cœur: No, that would crash at runtime. – Martin R Sep 10 '17 at 08:00
  • @MartinR That's normal to crash, for the same reasons as `_ = ["foo", "bar"][3]` would crash at runtime. Yet, we don't forbid Swift to implement subscripts that way. – Cœur Sep 10 '17 at 08:07
  • @Cœur: I don't get what you mean. I have tested the code with an actual type conforming to DDCDataSource. Changing the return type or adding a type alias (as I suggested below) makes it compile *and run* as expected, but with `return object[position] as! Element` it crashes at runtime. – Martin R Sep 10 '17 at 08:11
  • @MartinR With a valid index, it doesn't crash. I used `subscript(test position: Object.Index) -> Element { return object[position] as! Element }` and `extension Array: DDCDataSource { typealias Object = Array; var object: Array { get { return self } set { } } }`. Then I test and `print(["a", "b"][test: 1])` outputs **b** while `print(["a", "b"][test: 3])` gives a _fatal error: Index out of range_. – Cœur Sep 10 '17 at 08:33
  • @Cœur: I tested it with `struct MyDataSource: DDCDataSource { var object = [1, 2, 3] }` and `let mds = MyDataSource() ; print(mds[1])` – In your example, `Object.Element` and `Self.Element` are identical, that is not the case in general. – Martin R Sep 10 '17 at 08:38
  • @MartinR _Type 'MyDataSource' does not conform to protocol '_IndexableBase'_ and _Type 'MyDataSource' does not conform to protocol 'Collection'_. So your code doesn't build. Xcode 9 beta 6, Swift 3.2. – Cœur Sep 10 '17 at 08:44
  • @Cœur: That is strange. I have added the full example below, it works for me. – Martin R Sep 10 '17 at 08:49

3 Answers3

6

Short answer: Change the return type of the subscript method to Object.Element

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

or add a type alias (in a similar way as you did for the Index type)

typealias Element = Object.Element

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

That makes the code compile and run as expected.


Explanation: The subscript method of Collection is declared as

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

where Self.Index and Self.Element are associated types of `Collection. With your code

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

the compiler infers Self.Index to be Object.Index, but there is no relation between Self.Element and Object.Element (which is returned by object[position]). The error becomes more apparent if you add an explicit cast:

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

Now the compiler complains

error: 'Self.Object.Element' is not convertible to 'Self.Element'; did you mean to use 'as!' to force downcast?

The correct solution is not the forced cast but to make the compiler know that Self.Element is Object.Element, by adding a type alias or by changing the return type

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

so that the compiler infers DDCDataSource.Element to be Object.Element.


Full self-contained example: (Swift 4, Xcode 9 beta 6)

(Note that you can omit the get keyword for read-only computed properties.)

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
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
0

Firstly, I think you should define Element,

Secondly, you use object[position], Object Conforms To Collection , but it is not of Collection Types . Obviously it is not Array.

As apple says: array conforms to CustomDebugStringConvertible / CustomReflectable / CustomStringConvertible / CVarArg /Decodable / Encodable / ExpressibleByArrayLiteral /MutableCollection /RandomAccessCollection / RangeReplaceableCollection

I think extension DDCDataSource where Object: Array is better.

And the element in array shall be Element defined. Just tips.

dengApro
  • 3,848
  • 2
  • 27
  • 41
  • This is a protocol, the goal is to have it be very generic and allow classes/structs that use it to define Element. Also I want Object to work for all collection types and not just arrays. – Chris Wood Sep 10 '17 at 02:35
  • Yes, the protocol is brief. I try to simplify it . Somewhat divide and conquer. And thank you. I am new to Swift 4. `Element` seems weird to me. I got some , while not good as Martin R . No down vote is nice. – dengApro Sep 10 '17 at 14:47
0

Try this:

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

    }
Torongo
  • 1,021
  • 1
  • 7
  • 14
  • 2
    Does that really compile? `object[position]` is not an optional. – Martin R Sep 10 '17 at 08:01
  • Right, it's not an optional - but leaving out the guard statement and just returning object[position] as! Element would compile. As to whether it would crash or not -- I did not try to actually run it yet. – Chris Wood Sep 10 '17 at 11:14