Heterogeneous vs Homogeneous Containers in Swift


Update 15-July-2015: When I first started writing the article I wanted to cover the generic constraints related to Heterogeneous/Homogeneous containers. However I pivoted to only cover the containers; Hence the title was renamed from “Heterogeneous vs Homogeneous Containers in Swift” to “Heterogeneous vs Homogeneous Collections in Swift”


In the talk “Protocol-Oriented Programming in Swift” talk, Dave Abrahams introduced the concept of heterogeneous vs homogeneous containers.

While this two types of containers are not new to other programming languages, they are rather new in swift. In this article I will try to explain the difference in these two concepts.

Lets say we want to add a sumToArray(array) method to both Int and String structs. When calling this method on an Int, it will sum the array, and add the current integer to it. If we call it on a String, it will concatenate the array of strings and then append the current string on its tail.

1.sumToArray([1,2,3])
“a”.sumToArray([“1”,”2",”3"])

Calling the above would result to “7” and “123a”.

To approach this issue we start by defining a Summable protocol that contains the sumToArray.

protocol Summable {
func sumToArray(array: []) -> Summable
}

The array type is left empty as I will describe later on how this will affect the type of container we use.

Next we extend Int and String and add the conformance to the Summable protocol.

extension Int: Summable {}
extension String: Summable {}

We now have the setup to test out both the Heterogeneous and the Homogeneous container approaches.


Heterogeneous Containers


When using heterogeneous containers we declare the array in sumToArray to be of [Summable] type.

protocol Summable {
func sumToArray(array: [Summable]) -> Summable
}

By using a [Summable] array we opted to use heterogeneous containers; each item in the array conforms to the Summable protocol. Since both Int and String conforms to Summable. array can contain Ints, Strings or both.

As such, both of the bellow are valid calls to sumToArray

1.sumToArray([1, 2, 3, “2”])
“a”.sumToArray([“1”, “2”, “3”, 2 , 5])

This means that we need to type check the values of the items in the array before we can perform any operation on them.

For Int, it means we need to check that the item is an Int before adding it to our sum:

extension Int: Summable {
  func sumToArray(array: [Summable]) -> Summable {
    return array.reduce(self) { first, second in
if let second = (second as? Int) {
return first + second
}

return first
}
}
}

The same applies to String, we have to check if the item is a string.

extension String: Summable {
  func sumToArray(array: [Summable]) -> Summable {
    return array.reduce(self) { first, second in
if let second = (second as? String) {
return first + second
}
      return first
}
}
}

The [Summable] array is an example of a heterogeneous container; a container that contains different types. The only constraint on this array is each element conforms to Summable.


Homogeneous Containers


A Homogeneous container is a container that only contains items of the same type. In swift, when using the Self type annotation in a protocol, we opt to use homogeneous containers.

Lets update Summable to use the Self type annotation.

protocol Summable {
func sumToArray(array: [Self]) -> Self
}

Self type is a placeholder for the static type of the conformer to Summable protocol. This means that Self will be Int, when implementing sumToArray in Int extension, and String, when implementing it in String extension.

Bellow is the how we would implement sumToArray in both Int and String extensions.

extension Int: Summable {
func sumToArray(array: [Int]) -> Int {
return array.reduce(self, combine: +)
}
}
extension String: Summable {
func sumToArray(array: [String]) -> String {
return array.reduce(“”) { $0 + $1 } + self
}
}

Notice how [Self] has been rewritten to [Int] and [String]. Using Self in the protocol has eliminated the tedious type check that we had to do in the heterogeneous container example before.

By using Self we eliminated the need to do dynamic time type checking and replaced it with the safer compile time type assertion.

Since we opted to use homogeneous container, we cannot call sumToArray with both Ints and Strings.

sumToArray can only be called using an array of all Ints or Strings

1.sumToArray([1,2,3])
“a”.sumToArray([“1”,”2",”3"])

Conclusion


Swift protocols enable us to create heterogeneous and homogeneous containers. This can be controlled by using the Self type annotation that acts as a stand-in for the static type of the conformer struct, enum or class.