Reputation: 15588
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
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
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
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