D.C.
D.C.

Reputation: 15588

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

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.

Upvotes: 8

Views: 950

Answers (3)

Airspeed Velocity
Airspeed Velocity

Reputation: 40973

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?

Upvotes: 8

Gregory Higley
Gregory Higley

Reputation: 16598

In the case of protocols, it depends on the protocol itself. If the protocol uses Self or a typealias, it cannot be used directly. For any other protocol, you can declare variables and parameters of type protocol<MyProtocol>, e.g., var o: protocol<MyProtocol>.

The reason you can't say var o: protocol<Equatable> is because the Equatable protocol is designed in a way that certain constraints it declares (in this case Self) must be satisfied, and thus it can only be used as a generic type constraint. In other words, the compiler must be able to figure out at compile time what Self is in regards to anything that is Equatable, and it cannot (always) do that in var o: protocol<Equatable>.

Why use generics rather than protocols or base classes? Because generics can be even more general than those while still being type-safe. This is especially useful, for instance, with something like a callback. Here's a very contrived example:

class Useless<T> {
    private let o: T
    private let callback: (T, String) -> Void
    required init(o: T, callback: (T, String) -> Void) {
        self.o = o
        self.callback = callback
    }
    func publish(message: String) {
        callback(o, message)
    }
}

var useless = Useless(o: myObject) { obj, message in
   // Here in the callback I get type safety. 
   obj.someMethod(message)
}

(This code has never been run by anyone ever. It should be regarded as pseudo-code.)

Now, this is a pretty silly example for many reasons, but it illustrates the point. Thanks to generics, the obj parameter of the callback is entirely type-safe. This could not be done with base classes or protocols because we could never anticipate what code might get called in the callback. The Useless class can take any type as its T.

Upvotes: 5

bjtitus
bjtitus

Reputation: 4270

Your example is not really a good one, the second example doesn't compile because the Equatable protocol has references to Self and, as such, can't be used as a parameter type.

The other major benefit is that each generic identifier will constraint all parameters with that type as the same type. So, in your first example, both object and fromArray have to be of the same type, which your second example would not do.

Upvotes: 0

Related Questions