Search code examples
swiftgenericsgeneric-programminganyobject

swift generics return first and last element


I'm trying to get used to generics (never used them in objc) and want to write a toy function that takes an object of any type () and returns the first and last element. Hypothetically, I'd only use this on an array or a string - I keep getting an error that has no subscript members. I totally understand that the error message is telling me swift has no clue that T may potentially hold a type that does have subscripts - I just want to know how to get around this.

func firstAndLastFromCollection<T>(a:T?) {
    var count: Int = 0

    for item in a as! [AnyObject] {
        count++
    }
    if count>1 {
        var first = a?[0]
        var last = a?[count-1]
        return (first, last)
      }
    return something else here
 }

Do I need to typecast somewhere here (which would kind of defeat the purpose here, as I'd need to downcast as either a string or an array, adding code and lessening how generic this func is)?


Solution

  • If we are talking about collections, let's use the CollectionType:

    func firstAndLastFromCollection<T: CollectionType>(a: T) -> (T.Generator.Element, T.Generator.Element)? {
        guard !a.isEmpty else {
            return nil
        }
    
        return (a.first!, a.lazy.reverse().first!)
    }
    
    print(firstAndLastFromCollection(["a", "b", "c"])) // ("a", "c")
    print(firstAndLastFromCollection("abc".characters)) // ("a", "c")
    print(firstAndLastFromCollection(0..<200)) // (0, 199)
    print(firstAndLastFromCollection([] as [String]))   // nil
    

    If you specify your generic type to also conform to bidirectional index:

    func firstAndLastFromCollection<T: CollectionType where T.Index : BidirectionalIndexType>(...) -> ...
    

    then you can call last directly:

    return (a.first!, a.last!)
    

    If we decide to implement it using a category, we don't need generics at all:

    extension CollectionType {
        func firstAndLast() -> (Generator.Element, Generator.Element)? {
            guard !self.isEmpty else {
                return nil
            }
    
            return (self.first!, self.lazy.reverse().first!)
        }
    }
    
    extension CollectionType where Index: BidirectionalIndexType {
        func firstAndLast() -> (Generator.Element, Generator.Element)? {
            guard !self.isEmpty else {
                return nil
            }
    
            return (self.first!, self.last!)
        }
    }
    
    print("abc".characters.firstAndLast())
    

    Swift is a protocol oriented language. Usually you will find yourself extend protocols more than extending classes or structs.