Generic Array of Int - what, why, how?

One of the great feature introduced by Swift 2.0 is possibility to define generic functions for type of Array.

Array is container for values of some type. The type is known upfront. It's called Element now.

what?

Array is a struct with element and is defined like this:

struct Array<Element>

my goal is to create function that apply to array of integers, and integers only, specifically Int, not UInt, not Int32, not to array of string, or any other type. It has to be array of integer. This is my array:

let array:[Int] = [1,2,3,4,5]

now add function printValues() using extension:

Note: The implementation of function printValues() is not important at this point. I put "Hello world!" inside.

extension Array where Element: Int { // error
    func printValues() {
        print("Hello world!")
    }
}

though, this code raises error:

error: type Element constrained to non-protocol type Int

why?

Int is not a protocol, and can't be used with this definition. Kind of unexpected, but consistent with Type Constraint Syntax. At this point let's check the documentation:

"You write type constraint by placing a single class or protocol constraint after a type parameter's name, separated by a colon, as part of the type parameter list."

I especially emphasized here two terms: class and protocol, remember this for the following steps.

The problem is that Int is a struct, neither class nor protocol, and this is why can't be used in my definition to describe Element.

What if I add protocol, and make Int conform to that protocol? yes, that will do the trick.

protocol _IntType { }
extension Int: _IntType {}

from now on I can use _IntType to describe Int in constraints. Pretty handy:

extension Array where Element: _IntType {
    func printValues() {
        print("Hello world!")
    }
}

and now I can do this:

let array:[Int] = [1,2,3,4,5]
array.printValues() // Hello world!

how?

values

Let's go deeper. I will try to print all odd values of the array with function printOddValues()

extension Array where Element: _IntType {
    func printOddValues() {
        for i in self where i % 2 != 0 { // error
            print(i, terminator: ",")
        }
    }
}

error: binary operator % cannot be applied to operands of type Element and Int

Error is not especially helpful (though, informative enough). I know that Element is Int because of generic constraints. BUT... in fact, _IntType is just the protocol, as such, more than one other type may conform to that protocol. As a conclusion at this point, it is not universally true that _IntType is just for type Int (however it is in fact). Because this statement may not be true, it is evaluated as false (uff).

It's not really useful if I can't use values in my functions that apply to this type of array, is it? Fortunately it can be done.

One of the solution is force cast to known type. Not generic approach, but hey, I know that Element is type of Int so I can force and tell compiler to use my value as Int:

for i in self where (i as! Int) % 2 != 0 { /* */ }

quite straightforward, though not especially pretty. In addition it looks like it breaks some layer of abstraction.

value of type

Another situation is when I want to use my array (from generic extension) as parameter for another function. The problem is again: Element

for given:

extension Array where Element: _IntType {
    func sum() -> Int {
        return calculateSum(self) // error
    }
}

func calculateSum(ints: [Int]) -> Int {
    return ints.reduce(0, combine: +)
}

error: cannot convert value of type [Element] to expected argument type [Int]

"Ah yeah, you'd need to call it something other than value to avoid colliding with that"[1] - not obvious. Apparently value is taken.

I'll box it with intValue

protocol _IntType {
    var intValue: Int { get }
}

extension Int: _IntType {
    var intValue: Int {
        return Int(self.value)
    }
}

extension Array where Element: _IntType {
    func sum() -> Int {
        return calculateSum(self.map { return $0.intValue }) // $?!?@?@?!!!
    }
}

mindf****. Especially way to get values and pass to another function is far, faaaar from readability principles. Iterate over values just to get the value and return as Int, not Element really blow my mind:

self.map { return $0.intValue } // [Int]

arrayValue

Another way to deal with this is define "wrapper" around self that returns known type (Self) in place of Element. It looks hacky, it feels hacky, but it's working and is a bit easier to use:

protocol _IntType {
    static func arrayValue(array: [Self]) -> [Int] // here Int!
}

extension _IntType {
    static func arrayValue(array: [Self]) -> [Self] {
        return array
    }
}

extension Int: _IntType { }

protocol define arrayValue() then extension to protocol _IntType implements wrapper. In the last line Int conforms to _IntType with wrapper.

usage:

extension Array where Element: _IntType {

    func printOddValues() {
        for i in Element.arrayValue(self) where i % 2 != 0 {
            print(i, terminator: ",")
        }
    }

    func sum() -> Int {
        return calculateSum(Element.arrayValue(self))
    }
}

yes, it's kind of complicated, smells like hack, though it just a workaround ;-)

[1,2,3,4,5].sum() // 15
[1,2,3,4,5].printOddValues() // "1,3,5,"

One more thing

one more thing is that it all can[2] be build for _ArrayType, not Array. _ArrayType is the protocol Array conforms to, then extend ArrayType (not Array).

protocol ArrayType: _ArrayType {
    func arrayValue() -> [Generator.Element]
}

extension Array: ArrayType {
    func arrayValue() -> [Generator.Element] {
        return self
    }
}

extension ArrayType where Generator.Element == Int {
    func printOddValues() {
        for i in arrayValue() where i % 2 != 0 {
            print(i, terminator: ",")
        }
    }
    
    func sum() -> Int {
        return calculateSum(arrayValue())
    }
}

and that's good. However.... it's getting tricky, hard, and it brings the tears if I expect typical array operations. This is because _ArrayType is general top level type, while most of the functions to work with Array is implemented by Array, ArraySlice and ContinousArray.

Shortcut using IntegerLiteralType

Worth mention shortcut. Since I already have type IntegerType I can use it to mark "all integers", then specity one type in particular with constraints on

Element.IntegerLiteralType == UInt8

extension:

extension Array where Element: IntegerType, Element.IntegerLiteralType == UInt8 {
    // for Array<UInt8>
}

voila

Conclusions

It is possible to define and use functions for array of specific type. I wish it was easier.

PS

If you wonder why I didn't use IntegerType, here's my answer: Swift: Madness of Generic Integer. IntegerType is very general protocol and lack many functionality for integers. However most important is that this is just an example, it can be any other type.


  1. https://twitter.com/jckarter/status/648219154139377664 ↩︎

  2. https://twitter.com/Kametrixom/status/648250545019461632 ↩︎