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.
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?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With