Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift generics: return type based on parameter type

Tags:

Say I have a collection of objects inheriting from a common superclass (this is preferable to protocols in this case):

class ObjectSuperClass {     type: ObjectType } class ObjectClass1: ObjectSuperClass {     type = .Type1 } class ObjectClass2: ObjectSuperClass {     type = .Type2 } 

I'm looking to create a generic search function like this:

func objectsOfType<T: ObjectSuperClass>(T.class, otherFilter: Any?) -> [T] 

Which could be used to search for a given sub-type, returning a more specific array of results:

let result = objectsOfType(ObjectClass2.class, otherFilter: nil) -> [ObjectClass2] (pseudo-swift) 

I feel like this is somewhere generics could help, but cannot see where constraints should be placed. Is it possible?

like image 606
Ben Avatar asked Jan 04 '16 12:01

Ben


1 Answers

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 } } 
like image 58
JeremyP Avatar answered Oct 12 '22 20:10

JeremyP