Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding swift generics vs treating parameters as a protocol or base type

Tags:

generics

swift

Can someone help me understand the benefits of using generics over just using a base class or protocol? Perhaps I just need to read the Swift guide a few more times, but the concept of generics is just not sinking in. Consider this example using generics

func removeObject<T : Equatable>(object: T, inout fromArray array: [T]) 
{
    var index = find(array, object)
    array.removeAtIndex(index!)
}

Why not just write it like this?

// As pointed out, this does not compile. I was more-so curious as to why
func removeObject(object: Equatable, inout fromArray array: [Equatable]) 
{
    var index = find(array, object)
    array.removeAtIndex(index!)
}

Thanks for your explanations.


Update. Yes, to clarify my examples were entirely hypothetical. I was thinking about the problem in terms how I would do it in Objective-C. In that was I would just pass the parameters of type id, and that would do it.

My question was meant to get some insight about why a similar pattern is not permitted in Swift, and instead why generics are used instead.

like image 484
D.C. Avatar asked Dec 23 '14 23:12

D.C.


1 Answers

I think there has been a dev forum post by the Swift team about how it would be nice to be able to give the second example as shorthand for the first. However, I think it would still really be a generic function – just a shorthand for declaring one?

How would that be different? As the other answers have pointed out, Equatable can only be used generically. But let’s take an example that doesn’t have to be. How is this:

func f<T: Printable>(t: T) {
    // do some stuff
}

different from this:

func g(p: Printable) {
    // do some stuff
}

The difference is, f defines a family of functions that are generated at compile time, with whatever the type of what is passed in as t substituted for T.* So if you passed in an Int, it would be as if you’d written a version func f(t: Int) { … }. If you passed in a Double, it would be like writing func f(t: Double) { … }

* this is an over-simplification but go with it for now...

On the other hand, g is only one function, that at runtime can only accept a reference to a Printable protocol.

In practice the differences are almost imperceptible. For example, if you pass the t inside f to another function it acts like this:

func f(i: Int) {
    // h doesn’t receive an Int 
    // but a Printable:
    h(i as Printable)
}

So for example:

func h(i: Int) {
    println("An Int!")
}

func h(p: Printable) {
    println("A Printable!")
}

func f<T: Printable>(t: T) {
    h(t)
}

h(1) // prints "An Int!"
f(1) // prints "A Printable!"

You can see the difference in little ways though:

func f<T: Printable>(t: T) { 
    println(sizeof(t))
}

f(1 as Int8)  // prints 1
f(1 as Int64) // prints 8

The biggest difference is that they can return the actual generic type not a protocol:

func f<T: Printable>(t: T) -> T {
    return t
}

func g(p: Printable) -> Printable {
    return p
}

let a = f(1)    // a is an Int
let b = f([1])  // b is an [Int]

let c = g(1)    // c is a Printable
let d = g([1])  // d is a Printable

This last example is the key to understanding why protocols with associated types can only be used generically. Suppose you wanted to make your own implementation of first:

func first<C: CollectionType>(x: C) -> C.Generator.Element? {
    if x.startIndex != x.endIndex {
        return x[x.startIndex]
    }
    else {
        return nil
    }
}

If first wasn’t a generic function, and just a regular function that received an argument of a CollectionType protocol, how would it be possible to vary what it returned?

like image 125
Airspeed Velocity Avatar answered Nov 16 '22 04:11

Airspeed Velocity