Search code examples
swiftgenericsswift-extensions

Swift Generics Type Inference Extensions


I am trying to build my understanding of Generics in Swift by creating a Min and Max extension for the Array class (similar to Min and Max Extension methods in C#). There may be a better way to do this, but as I said, it just to help me understand Generics.

I have created the following code:

extension Array {
    func max<T, U : Comparable>(f: T -> U ) -> U? {
        var maxSoFar : U? = nil
        for i in self {
            var itemValue = f(i as T)
            if(maxSoFar == nil) {
                maxSoFar = itemValue
            }
            if itemValue > maxSoFar {
                maxSoFar = itemValue
            }
        }
        return maxSoFar
    }

    func min<T, U : Comparable>(f: T -> U ) -> U? {
        var minSoFar : U? = nil
        for i in self {
            var itemValue = f(i as T)
            if(minSoFar == nil) {
                minSoFar = itemValue
            }
            if itemValue < minSoFar {
                minSoFar = itemValue
            }
        }
        return minSoFar
    }
}

To test, I have created a basic Person class:

class Person {
    var name : String
    var age : Float

    init(name: String, age: Float) {
        self.name = name
        self.age = age
    }
}

It seems to work fine for these cases when I am explicit in the closure:

var maximumAge = [Person(name: "Bob", age: 42), Person(name:"Mary", age:40)]
    .max{ (p: Person) in p.age }! // Gives 42

var min = [100, 101].min{ (i: Int) in i }! // Gives 100

However, I cannot get it to infer the types for using the extreme shorthand case:

var maximumAge = [Person(name: "Bob", age: 42), Person(name:"Mary", age:40)]
    .max{ $0.age }! // Error

var min = [100, 101].min{ $0 }! // Error

Or the medium length:

var maximumAge = [Person(name: "Bob", age: 42), Person(name:"Mary", age:40)]
    .max{p in p.age }! // Error

var min = [100, 101].min{ i in i }! // Error

If anyone is an expert on this yet, can you let me know what I am doing wrong? I have to admit it took me a fair bit of reading and hacking to get this far!

Thanks in advance for any replies


Solution

  • When you define max (and min) like this:

    func max<T, U : Comparable>(f: T -> U ) -> U?
    

    You're actually saying that the closure f might take a different type than the elements in the Array. Since Array is already a generic struct Array<T>, you can re-use the element type T that it's already defined. From the Swift language guide:

    When you extend a generic type, you do not provide a type parameter list as part of the extension’s definition. Instead, the type parameter list from the original type definition is available within the body of the extension, and the original type parameter names are used to refer to the type parameters from the original definition.

    So, since the element type T is already available to you, simply take the T out of the type parameter list, like so:

    func max<U : Comparable>(f: T -> U ) -> U?
    

    That guarantees that the closure f will take the same type T that the elements in the Array have. Then the compiler can correctly infer which types you are using in your test cases and fixes the errors you're seeing.