Use Indexable as a type

I have an object that I would like to be indexable, just like an Array is, and then have a function return an 'Indexable' (this way, the function could return either an instance of my custom class, or an Array).


So, the use of a custom would be like so:

let myObject = MyObject()
myObject[0] = 8
let value = myObject[0]
print("value: \(value)")

// output should be:
// 'value 8'


Step 1 is to make this object indexable:

Class MyObject
{
    subscript(index: UInt16) -> UInt8 {
       get
        {
    // return a value based on custom logic using the index
        }
        set

        {
           // set a value based on custom logic using the index
        }

    }
}


Step 2 is to make sure Array can be indexed using the same values, so I have made an extension on Array to also be able to give me value based on UInt16 index, as my Arrays are [UInt8], the return element is UInt8

extension Array
{
    subscript(index: UInt16) -> Element {
        get
        {
            return self[Int(index)]
        }
        set(newValue)
        {
            self[Int(index)] = newValue
        }
    }
}


Now here is the difficult part: I would like my function to return something like 'Indexable', so that a client of this function is unaware of whether the underlying type is an Array ([UInt8]), or an instance of MyObject:

func source(forValue value: UInt16) -> ???
{
     /// switch on the value, and return either an instance of MyObject or Array
}


However, I cannot use the already existsing 'Indexable' protocol, as the compiler will give me this error:

"Protocol 'Indexable' can only be used as a generic constraint because it has Self or associated type requirements".

I can read the Swift book and see why this is an error, but I have hard time figuring out what would another way to make this happen.

I cannot create my own protocol, and 'override' the subscripting on Array, as that will turn into a recursion.


Compare this to Objective-C, where I could simply implement the subscript methods, and use 'id' as type (not saying that is the better way, but it seems in Obj-C you could always just fall back to id, which is not safe, but would work if you were carefull, and in Swift we are at the other end of the spectrum: because all code has to be fully typed, things like this that should be easy, are now really hard to accomplish, and are a distraction from the actual task I want to do).


So the quesion is: how to mix custom objects with Array such that a function can return a type that indicates only that this type can be indexed?

I don’t think it’s possible to do what you what, at least not directly. Consider this protocol:

protocol MyIndexable {
    subscript(index: UInt16) -> UInt8 { get }
}

You want both

MyObject
and
Array<UInt8>
to conform to that. The first part is easy, the second is hard. Let’s give it a go.

Firstly, you can’t use concrete types in generic constraints, so we make up a dummy protocol and have

UInt8
conform to that.
protocol MyElement {
}

extension UInt8 : MyElement {
}

Next, we try to extend array based on that.

extension Array : MyIndexable where Element: MyElement {
    subscript(index: UInt16) -> UInt8 { return 0 }
}

This doesn’t work because Swift does not (currently) support conditional conformance. This is a well known limitation. If you haven't already read the Generics Manifesto, you should do so.

WARNING While that doc’s discussion of conditional conformance is easy to understand, there are other sections of the doc that will melt your brain. Make sure you install an adequate brain cooling device before attempting to read the entire doc.

Is there a reason you want to use

Array<UInt8>
directly here? Are the clients who call the
source(forValue:)
function going to be doing other, array-like things with that array? If not, you could just wrap
Array<UInt8>
in your own structure and have that structure conform to
MyIndexable
.
struct MyByteArray : MyIndexable {
    var contents: [UInt] = []
    subscript(index: UInt16) -> UInt8 { return 0 }
}

While this adds a level of indirection, its actual runtime cost is typically small because in most cases the compiler can see through the abstraction.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks for your suggestions. The reason I want to use an Array<UInt8> directly is simple convenience. The array is used in a loop that will be a critical part of performance, so I was trying to avoid additional runtime overhead.But at the moment, performance is not my concern so I can go with this.

I didn't want to go with creating a wrappr, as creating a wrapper for an array to support functionality that is already there seemed a but clumsy to me.

I will read the Generics Manifesto now (as long as my brains stay below the critical temperatue, thanks for the warning).

The array is used in a loop that will be a critical part of performance, so I was trying to avoid additional runtime overhead.

Don’t assume that adding an abstraction layer will cause performance problems. In many cases the Swift compiler can remove abstraction layers automatically, allowing you to write clean code that still runs really fast.

While this talk is quite old now, I found it really interesting.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Wow, this is indeed really interesting. Do you happen to know if these kind of compiler-behaviours are documented somewhere (other then looking at compiler-sourcecode or assemblycode)?

Wow, this is indeed really interesting.

Indeed. And we’ve just learnt that the speaker is joining Apple to work on Swift. Yay!

Do you happen to know if these kind of compiler-behaviours are documented somewhere (other then looking at compiler-source code or assemblycode)?

I’ve not seen any doc that addresses this as a whole. Rather, you have to pull together points from a bunch of different docs. With 20:20 hindsight it would have been clever for me to have maintained a list of those docs over the past couple of years, but, alas, that did not happen )-:

Having said that, I do have one other great reference, namely WWDC 2016 Session 416 Understanding Swift Performance. Most enlightening.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
Use Indexable as a type
 
 
Q