Swift generics: return type based on parameter type

Well remarkably this works...

func filterType<T>(list: [AnyObject]) -> [T]
{
    return list.filter{ $0 is T }.map{ $0 as! T }
}

...provided you assign the result to something that has been explicitly typed, as in the following example:

class ObjectSuperClass: CustomStringConvertible
{
    let myType: String
    init(aString: String)
    {
        myType = aString
    }
    var description: String { return myType }
}

class ObjectClass1: ObjectSuperClass
{
    init()
    {
        super.init(aString: "<t 1>")
    }
}

class ObjectClass2: ObjectSuperClass
{
    init()
    {
        super.init(aString: "<t 2>")
    }
}

let unfilteredList: [AnyObject] = [ ObjectClass1(), ObjectClass2(), ObjectSuperClass(aString: "<Who knows>")]

let filteredList1: [ObjectClass1] = filterType(list: unfilteredList)
print("\(filteredList1)") // <t 1>

let filteredList2: [ObjectClass2] = filterType(list: unfilteredList)
print("\(filteredList2)") // <t 2>

let filteredList3: [ObjectSuperClass] = filterType(list: unfilteredList)
print("\(filteredList3)") // [<t 1>, <t 2>, <Who knows>]

T is inferred in each case from the requested return type. The function itself filters the original array based on whether the elements are of the required type and then force casts the filtered results to the correct type.


If you want an "extra filter" you don't need to explicitly type the results as long as T can be inferred from your extra filter function.

func extraFilterType<T>(list: [AnyObject], extraFilter: T -> Bool) -> [T]
{
    return list.filter{ $0 is T }.map{ $0 as! T }.filter(extraFilter)
}

let filteredList = extraFilterType(unfilteredList){
    (element : ObjectClass2) -> Bool in
    !element.description.isEmpty
}

print("\(filteredList)") // <t 2>

EDIT

A slicker version of the filterType function would use flatMap()

func filterType<T>(list: [Any]) -> [T]
{
    return list.flatMap{ $0 as? T }
}

EDIT 2

Flatmap is deprecated for optionals, since Swift 4.something, use compactMap

func filterType<T>(list: [Any]) -> [T]
{
    return list.compactMap{ $0 as? T }
}

You can implement the function like this:

func objectsOfType<T: ObjectSuperClass>(objects: [ObjectSuperClass], subclass: T.Type, otherFilter: (T->Bool)?) -> [T] {
    if let otherFilter = otherFilter {
        return objects.filter{$0 is T && otherFilter($0 as! T)}.map{$0 as! T}
    } else {
        return objects.filter{$0 is T}.map{$0 as! T}
    }
}

Usage example:

objectsOfType(arrayOfObjects, subclass: ObjectClass1.self, otherFilter: nil)

Note that I'm not a fan of forced casting, however in this scenario it should not cause problems.

Or, the more verbose version of the function, with one less forced cast:

func objectsOfType<T: ObjectSuperClass>(objects: [ObjectSuperClass], subclass: T.Type, otherFilter: (T->Bool)?) -> [T] {
    return objects.filter({object in
        if let object = object as? T {
            if let otherFilter = otherFilter {
                return otherFilter(object)
            } else {
                return true
            }
        } else {
            return false
        }
    }).map({object in
        return object as! T
    })
}

This is the closest approximation I can come up with:

func objectsOfType<T: ObjectSuperClass>(type type: T.Type) -> [T] {
    // Just returns an array of all objects of given type
}

func objectsOfType<T: ObjectSuperClass>(type type: T.Type, predicate: T -> Bool) -> [T] {
    // Uses predicate to filter out objects of given type
}

Usage:

let bar = objectsOfType(type: ObjectClass1.self)

let baz = objectsOfType(type: ObjectClass2.self) {
    // Something that returns Bool and uses $0
}

Technically, you can also go without type argument in the above, but then you will need to have explicitly typed receivers (bar and baz in the above example) so that Swift can correctly infer the types for you and use the right version of the generic function.

Tags:

Generics

Swift