Arrays of Generics in Swift

ArraysSwiftGenericsSwift Protocols

Arrays Problem Overview


I've been playing around with arrays of generic classes with different types. It's easiest to explain my problem with some sample code:

// Obviously a very pointless protocol...
protocol MyProtocol {
    var value: Self { get }
}

extension Int   : MyProtocol {  var value: Int    { return self } }
extension Double: MyProtocol {  var value: Double { return self } }

class Container<T: MyProtocol> {
    var values: [T]
    
    init(_ values: T...) {
        self.values = values
    }

    func myMethod() -> [T] {
        return values
    }
}

Now if I try to create an array of containers like so:

var containers: [Container<MyProtocol>] = []

I get the error:

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

To fix this I can use [AnyObject]:

let containers: [AnyObject] = [Container<Int>(1, 2, 3), Container<Double>(1.0, 2.0, 3.0)]
// Explicitly stating the types just for clarity.

But now another 'problem' emerges when enumerating through containers:

for container in containers {
    if let c = container as? Container<Int> {
        println(c.myMethod())
    
    } else if let c = container as? Container<Double> {
        println(c.myMethod())
    }
}

As you can see in the code above, after determining the type of container the same method is called in both cases. My question is:

Is there a better way to get the Container with the correct type than casting to every possible type of Container? Or is there something else I've overlooked?

Arrays Solutions


Solution 1 - Arrays

There is a way - sort of - to do what you want - kind of. There is a way, with protocols, to eliminate the type restriction and still get the result that you want, kind of, but it isn't always pretty. Here is what I came up with as a protocol in your situation:

protocol MyProtocol {
    func getValue() -> Self 
}

extension Int: MyProtocol {
    func getValue() -> Int {
        return self
    }
}

extension Double: MyProtocol {
    func getValue() -> Double {
        return self
    }
}

Note that the value property that you originally put in your protocol declaration has been changed to a method that returns the object.

That's not very interesting.

But now, because you've gotten rid of the value property in the protocol, MyProtocol can be used as a type, not just as a type constraint. Your Container class doesn't even need to be generic anymore. You can declare it like this:

class Container {
    var values: [MyProtocol]

    init(_ values: MyProtocol...) {
        self.values = values
    }

    func myMethod() -> [MyProtocol] {
        return values
    }
}

And because Container is no longer generic, you can create an Array of Containers and iterate through them, printing the results of the myMethod() method:

var containers = [Container]()

containers.append(Container(1, 4, 6, 2, 6))
containers.append(Container(1.2, 3.5))

for container in containers {
    println(container.myMethod())
}

//  Output: [1, 4, 6, 2, 6]
//          [1.2, 3.5]

The trick is to construct a protocol that only includes generic functions and places no other requirements on a conforming type. If you can get away with doing that, then you can use the protocol as a type, and not just as a type constraint.

And as a bonus (if you want to call it that), your array of MyProtocol values can even mix different types that conform to MyProtocol. So if you give String a MyProtocol extension like this:

extension String: MyProtocol {
    func getValue() -> String {
        return self
    }
}

You can actually initialize a Container with mixed types:

let container = Container(1, 4.2, "no kidding, this works")

[Warning - I am testing this in one of the online playgrounds. I haven't been able to test it in Xcode yet...]

Edit:

If you still want Container to be generic and only hold one type of object, you can accomplish that by making it conform to its own protocol:

protocol ContainerProtocol {
    func myMethod() -> [MyProtocol]
}

class Container<T: MyProtocol>: ContainerProtocol {
    var values: [T] = []
    
    init(_ values: T...) {
        self.values = values
    } 
    
    func myMethod() -> [MyProtocol] {
        return values.map { $0 as MyProtocol }
    }
}

Now you can still have an array of [ContainerProtocol] objects and iterate through them invoking myMethod():

let containers: [ContainerProtocol] = [Container(5, 3, 7), Container(1.2, 4,5)]

for container in containers {
    println(container.myMethod())
}

Maybe that still doesn't work for you, but now Container is restricted to a single type, and yet you can still iterate through an array of ContainterProtocol objects.

Solution 2 - Arrays

This is a good example of "what did you want to happen?" And actually demonstrates the complexity that explodes if Swift had really first-class types.

protocol MyProtocol {
    var value: Self { get }
}

Great. MyProtocol.value returns whatever type implements it, remembering that this must be determined at compile time, not runtime.

var containers: [Container<MyProtocol>] = []

So, determined at compile-time, what type is this? Forget the compiler, just do it on paper. Yeah, not sure what type that would be. I mean concrete type. No metatypes.

let containers: [AnyObject] = [Container<Int>(1, 2, 3), Container<Double>(1.0, 2.0, 3.0)]

You know you're going down the wrong road when AnyObject has snuck into your signatures. Nothing about this is ever going to work. After AnyObject is only sackcloth.

> Or is there something else I've overlooked?

Yes. You need a type, and you haven't provided one. You've provide a rule for constraining a type, but no actual type. Go back to your real problem, and think about it more deeply. (Metatype analysis is almost never your "real" problem unless you're working on a CS PhD, in which case you'd be doing this in Idris, not Swift.) What actual problem are you solving?

Solution 3 - Arrays

This can be better explained with protocols like Equatable. You cannot declare an array [Equatable] because while two Int instances can be compared to each other and two instances of Double can be compared to each other, you cannot compare an Int to a Double although they both implement Equatable.

MyProtocol is a protocol, that means it provides a generic interface. Unfortunately, you have also used Self in the definition. That means that every type that conforms to MyProtocol will implement it differently.

You have written it yourself - Int will have value as var value: Int while a MyObject will have value as var value: MyObject.

That means that a struct/class that conforms to MyProtocol cannot be used in place of another struct/class that conforms to MyProtocol. That also means that you cannot use MyProtocol in this way, without specifying a concrete type.

If you replace that Self with a concrete type, e.g. AnyObject, it will work. However, currently (Xcode 6.3.1) it triggers a segmentation error when compiling).

Solution 4 - Arrays

If you try this modified example in a playground, it will systematically crash:

// Obviously a very pointless protocol...
protocol MyProtocol {
    var value: Int { get }
}

extension Int   : MyProtocol {  var value: Int    { return self } }
//extension Double: MyProtocol {  var value: Double { return self } }

class Container<T: MyProtocol> {
    var values: [T]
    
    init(_ values: T...) {
        self.values = values
    }
}


var containers: [Container<MyProtocol>] = []

Probably they are still working on this, and things might change in the future. Anyway as of now, my explanation for this is that a protocol is not a concrete type. Thus you do not now how much space in ram something conforming to the protocol will take (for example an Int might not occupy the same amount of ram as a Double). Thus it might be quite a tricky problem the allocation of the array in ram. Using an NSArray you are allocation an array of pointers (pointers to NSObjects) and they all occupy the same amount of ram. You can think of the NSArray as an array of the concrete type "pointer to NSObject". Thus no problem calculating ram allocation.

Consider that Array as well as Dictionary in Swift are Generic Struct, not objects containing pointers to objects as in Obj-C.

Hope this helps.

Solution 5 - Arrays

I changed the array declaration to be an array of AnyObject so that filter, map and reduce could be used (and also added in a few more objects to check for).

let containers: [AnyObject] = [Container<Int>(1, 2, 3), Container<Double>(1.0, 2.0, 3.0), "Hello", "World", 42]

This will allow you to check for type in the array and filter before you loop through the array

let strings = containers.filter({ return ($0 is String) })

println(strings) // [Hello, World]

for ints in containers.filter({ return ($0 is Int) }) {
    println("Int is \(foo)") // Int is 42
}

let ints = containers.filter({ return ($0 is Container<Int>) })
// as this array is known to only contain Container<Int> instances, downcast and unwrap directly
for i in ints as [Container<Int>] {
    // do stuff
    println(i.values) // [1, 2, 3]
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionABakerSmithView Question on Stackoverflow
Solution 1 - ArraysAaron RasmussenView Answer on Stackoverflow
Solution 2 - ArraysRob NapierView Answer on Stackoverflow
Solution 3 - ArraysSulthanView Answer on Stackoverflow
Solution 4 - ArraysMatteo PiomboView Answer on Stackoverflow
Solution 5 - ArraysubersnackView Answer on Stackoverflow