Understanding fast generics versus handling parameters as a protocol or base type

Can someone help me understand the benefits of using generics only when using a base class or protocol? Maybe I just need to read the Swift manual a few more times, but the concept of generics just does not dive. Consider this example with 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 explanation.


Update Yes, to clarify my examples were completely hypothetical. I thought about the problem in terms of how I would do this in Objective-C. In this, I would just pass the type identifier parameters, and that would do it.

My question was to get an idea of ​​why a similar template is not allowed in Swift, and instead generics are used instead.

+6
source share
3 answers

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

The reason you cannot say var o: protocol<Equatable> is because the Equatable protocol Equatable designed in such a way that certain restrictions that it declares (in this case Self ) must be satisfied, and therefore it can be used only as a general type restriction. In other words, the compiler should be able to find out at compile time that Self refers to everything that Equatable is and cannot (always) do this in var o: protocol<Equatable> .

Why use generics rather than protocols or base classes? Because generics can be even more general than with safety. This is especially useful, for example, 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 considered as pseudo code.)

Now, this is a pretty dumb example for many reasons, but it illustrates the point. Thanks to the generics, the callback obj parameter is completely type safe. This cannot be done with base classes or protocols, because we could never have predicted what code could be called in the callback. The Useless class can take any type as T

+5
source

I think the Swift development team presented a developer forum on how it would be nice to give a second example as a shorthand for the first. However, I think this will still be a common function - just a shorthand for declaring one?

How would it be otherwise? As the other answers pointed out, Equatable can only be used in general terms. But let's take an example that should not be. Like this:

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

different from this:

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

The difference is that f defines a family of functions that are generated at compile time, regardless of the type of what is passed as t , replaced by t . * Therefore, if you passed in Int , it would be as if youd wrote a version of func f(t: Int) { … } . If you go to Double , it will be like writing func f(t: Double) { … }

* This is an oversimplification, but continue with it for now ...

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

In practice, the differences are almost imperceptible. For example, if you pass 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 the smallest ways:

 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 the 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 related types can only be used in general terms. 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 not a generic function and just a regular function that received a CollectionType protocol argument, how could it change what it returned?

+6
source

Your example is not very good, the second example does not compile, because there are references to Self in the Equatable protocol and, therefore, cannot be used as a parameter type.

Another important advantage is that each generic identifier will limit all parameters with this type to the same type. So, in your first example, both object and fromArray must be of the same type, which your second example will not do.

0
source

Source: https://habr.com/ru/post/980099/


All Articles