I'm new to generics in swift, and while reading some books, I came across something I don't understand. In generic functions, when is it appropriate to use the type parameter (the right after the function name)? and when is it inappropriate?
Here an example where it is not used (signature only; from standard library):
func sorted(isOrderedBefore: (T, T) -> Bool) -> Array<T>
Here's an example of where it is used (taken from a book I'm reading):
func emphasize<T>(inout array:[T], modification:(T) -> T) {
for i in 0 ..< array.count {
array[i] = modification(array[i])
}
}
I read Apple's swift language reference, section: Generic Parameters and Arguments. But it is still not clear to me. Thanks in advance for any insight.
In the first example, the generic parameter is coming from the type it is defined within. I believe it is declared within Array which already has the generic type T.
In the second example, the function itself is declaring the generic parameter. If I am not mistaken, this function is a global function. It is not already within a scope that defines a generic T.
It is only inappropriate to declare a new generic parameter in a function that obscures or tries to replace the one already declared in it's scope. For example, when extending an array, this would be inappropriate:
extension Array {
func myFunc<T>() {
}
}
Here we are defining a new T that is obscuring the original T that is already declared in the declaration of Array.
In all other circumstances where you want a generic type, you should be declaring it yourself.
Related
In the Swift documentation, I came across a section which was saying that I can write type constraints like a method or a protocol. However, what I didn't understand were the benefits of writing this way. Why don't simply pass the class (or protocol) itself?
Documentation: https://docs.swift.org/swift-book/LanguageGuide/Generics.html
Let me explain with an example.
import Foundation
class A {
init(val: String) {
self.val = val
}
let val: String
}
func someFunction<T: A>(someT: T) {
print(someT.val)
}
func someFunction2(someT: A) {
print(someT.val)
}
// method 1
someFunction(someT: A(val: "asdasd"))
// method 2
someFunction2(someT: A(val: "asdasd"))
In the snippet below, what is the difference between method 1 and method 2?
In your overly simplified example, there's not much difference.
However, as soon as your type constraints become more complex, the only way to achieve the desired interface is via generics.
A great example if when you want a function to take an input argument whose type is a protocol with an associated type, such as SwiftUI's View.
If you tried to use View as the input argument type, you'd get a compile time error
func modifyView(_ view: View) {
}
Protocol 'View' can only be used as a generic constraint because it has Self or associated type requirements
However, as soon as you make view generic with a type constraint on View, the code compiles.
func modifyView<V: View>(_ view: V) {
}
You don't have to look at custom functions, the standard library is full of generic functions as well. JSONDecoder.decode showcases another common use case for generics, when you want your function to return a specific type based on some input arguments. This enables the developer to only write the function 1x, but make it work on lots of different types, while keeping type safety.
func decode<T: Decodable>(_ type: T.Type, from data: Data) throws -> T
Given a protocol without any associated types:
protocol SomeProtocol
{
var someProperty: Int { get }
}
What is the difference between these two functions, in practice (meaning not "one is generic and the other is not")? Do they generate different code, do they have different runtime characteristics? Do these differences change when the protocol or functions become non-trivial? (since a compiler could probably inline something like this)
func generic<T: SomeProtocol>(some: T) -> Int
{
return some.someProperty
}
func nonGeneric(some: SomeProtocol) -> Int
{
return some.someProperty
}
I'm mostly asking about differences in what the compiler does, I understand the language-level implications of both. Basically, does nonGeneric imply a constant code size but slower dynamic dispatch, vs. generic using a growing code size per type passed, but with fast static dispatch?
(I realise that OP is asking less about the language implications and more about what the compiler does – but I feel it's also worthwhile also to list the general differences between generic and protocol-typed function parameters)
1. A generic placeholder constrained by a protocol must be satisfied with a concrete type
This is a consequence of protocols not conforming to themselves, therefore you cannot call generic(some:) with a SomeProtocol typed argument.
struct Foo : SomeProtocol {
var someProperty: Int
}
// of course the solution here is to remove the redundant 'SomeProtocol' type annotation
// and let foo be of type Foo, but this problem is applicable anywhere an
// 'anything that conforms to SomeProtocol' typed variable is required.
let foo : SomeProtocol = Foo(someProperty: 42)
generic(some: something) // compiler error: cannot invoke 'generic' with an argument list
// of type '(some: SomeProtocol)'
This is because the generic function expects an argument of some type T that conforms to SomeProtocol – but SomeProtocol is not a type that conforms to SomeProtocol.
A non-generic function however, with a parameter type of SomeProtocol, will accept foo as an argument:
nonGeneric(some: foo) // compiles fine
This is because it accepts 'anything that can be typed as a SomeProtocol', rather than 'a specific type that conforms to SomeProtocol'.
2. Specialisation
As covered in this fantastic WWDC talk, an 'existential container' is used in order to represent a protocol-typed value.
This container consists of:
A value buffer to store the value itself, which is 3 words in length. Values larger than this will be heap allocated, and a reference to the value will be stored in the value buffer (as a reference is just 1 word in size).
A pointer to the type's metadata. Included in the type's metadata is a pointer to its value witness table, which manages the lifetime of value in the existential container.
One or (in the case of protocol composition) multiple pointers to protocol witness tables for the given type. These tables keep track of the type's implementation of the protocol requirements available to call on the given protocol-typed instance.
By default, a similar structure is used in order to pass a value into a generic placeholder typed argument.
The argument is stored in a 3 word value buffer (which may heap allocate), which is then passed to the parameter.
For each generic placeholder, the function takes a metadata pointer parameter. The metatype of the type that's used to satisfy the placeholder is passed to this parameter when calling.
For each protocol constraint on a given placeholder, the function takes a protocol witness table pointer parameter.
However, in optimised builds, Swift is able to specialise the implementations of generic functions – allowing the compiler to generate a new function for each type of generic placeholder that it's applied with. This allows for arguments to always be simply passed by value, at the cost of increasing code size. However, as the talk then goes onto say, aggressive compiler optimisations, particularly inlining, can counteract this bloat.
3. Dispatch of protocol requirements
Because of the fact that generic functions are able to be specialised, method calls on generic arguments passed in are able to be statically dispatched (although obviously not for types that use dynamic polymorphism, such as non-final classes).
Protocol-typed functions however generally cannot benefit from this, as they don't benefit from specialisation. Therefore method calls on a protocol-typed argument will be dynamically dispatched via the protocol witness table for that given argument, which is more expensive.
Although that being said, simple protocol-typed functions may be able to benefit from inlining. In such cases, the compiler is able to eliminate the overhead of the value buffer and protocol and value witness tables (this can be seen by examining the SIL emitted in a -O build), allowing it to statically dispatch methods in the same way as generic functions. However, unlike generic specialisation, this optimisation is not guaranteed for a given function (unless you apply the #inline(__always) attribute – but usually it's best to let the compiler decide this).
Therefore in general, generic functions are favoured over protocol-typed functions in terms of performance, as they can achieve static dispatch of methods without having to be inlined.
4. Overload resolution
When performing overload resolution, the compiler will favour the protocol-typed function over the generic one.
struct Foo : SomeProtocol {
var someProperty: Int
}
func bar<T : SomeProtocol>(_ some: T) {
print("generic")
}
func bar(_ some: SomeProtocol) {
print("protocol-typed")
}
bar(Foo(someProperty: 5)) // protocol-typed
This is because Swift favours an explicitly typed parameter over a generic one (see this Q&A).
5. Generic placeholders enforce the same type
As already said, using a generic placeholder allows you to enforce that the same type is used for all parameters/returns that are typed with that particular placeholder.
The function:
func generic<T : SomeProtocol>(a: T, b: T) -> T {
return a.someProperty < b.someProperty ? b : a
}
takes two arguments and has a return of the same concrete type, where that type conforms to SomeProtocol.
However the function:
func nongeneric(a: SomeProtocol, b: SomeProtocol) -> SomeProtocol {
return a.someProperty < b.someProperty ? b : a
}
carries no promises other than the arguments and return must conform to SomeProtocol. The actual concrete types that are passed and returned do not necessarily have to be the same.
If your generic method had more than one parameter involving T, there would be a difference.
func generic<T: SomeProtocol>(some: T, someOther: T) -> Int
{
return some.someProperty
}
In the method above, some and someOther have to be the same type. They can be any type that conforms to SomeProtocol, but they have to be the same type.
However, without generics:
func nonGeneric(some: SomeProtocol, someOther: SomeProtocol) -> Int
{
return some.someProperty
}
some and someOther can be different types, as long as they conform to SomeProtocol.
What is the difference between an any type and generic type in swift?
Any Type Example:
let swiftInt: Int = 1
let swiftString: String = "miao"
var array: [Any] = []
array.append(swiftInt)
array.append(swiftString)
Generic Type Example:
func duplicate<T>(item: T, numberOfTimes n: Int) -> [T] {
var buffer : [T] = []
for _ in 0 ..< n {
buffer.append(item)
}
return buffer
}
Is this a matter of preference because both appear to solve the same problem by being able to substitute the desired type.
I'm not going to explain generics in details and i'll just point out the essential differences.
In the first example, you'll be able to append any type in that array, without being able to restrict beforehand your array to a specific type and to leverage compile time checks to guarantee that the array will not contain extraneous types. Not much to see in that example.
The second example contains instead a generic function that provides all of the above functionalities, consistency checks on the content of the array will come for free and if you want you'll also be able to specify additional characteristics of that generic type T, like requesting that it implements a specific protocol (e.g. limit duplicate() to object that implement Comparable or Equatable).
But that is just a simple example of a generic function, you can also have parametrized classes (what you'll use the most) and there are a lot of additional functionalities.
Never use Any as a poor-man generics, real generics are way more flexible, add useful checks and make more explicit your intentions, with minimal additional effort required to implement them.
Any means "I don't want any type checking and I won't be able to call type-specific methods without casting"
For example, try to call:
var array: [Any] = [1, 2]
var sum = array[0] + array[1] // you cannot do this! you have to cast to Int first
A generic type is a placeholder for a type. When used, a concrete type is used instead of it (e.g. an Int or a String).
In short, never use Any. There are very very few specific situations when Any is what you want to use.
I'm very new to Swift but I have some experience with OO-programming. I've started to try and use parameterized classes in Swift and I have come across a strange design feature when overloading methods. If I define the following classes:
class ParameterClassA {
}
class ParameterClassB: ParameterClassA {
}
class WorkingClassA<T: ParameterClassA> {
func someFunction(param: T) -> Void {
}
}
class WorkingClassB: WorkingClassA<ParameterClassB> {
override func someFunction(param: ParameterClassA) {
}
}
Then the code compiles fine. However, as you'll notice, I've overloaded the function that normally uses the parameter type, which in my example is ParameterClassB, and given it a parameter of type ParameterClassA. How is that supposed to work? I know that it's not allowed in Java, and I'm wondering how the type parameter is interpreted. Can it be anything from the class hierarchy of the type parameter?
Also note that the problem is exactly the same if I remove the type parameter constraint : ParameterClassA in WorkingClassA.
If I remove the override keyword, then I get a compiler error requesting that I add it.
Thanks a lot for any explanation!
It has nothing at all to do with the generics (what you call "parameterized"). It has to do with how one function type is substitutable for another in Swift. The rules is that function types are contravariant with respect to their parameter types.
To see this more clearly, it will help to throw away all the misleading generic stuff and the override stuff, and instead concentrate directly on the business of substituting one function type for another:
class A {}
class B:A {}
class C:B {}
func fA (x:A) {}
func fB (x:B) {}
func fC (x:C) {}
func f(_ fparam : B -> Void) {}
let result1 = f(fB) // ok
let result2 = f(fA) // ok!
// let result3 = f(fC) // not ok
We are expected to pass to the function f as its first parameter a function of type B -> Void, but a function of type A -> Void is acceptable instead, where A is superclass of B.
But a function of type C -> Void is not acceptable, where C is subclass of B. Functions are contravariant, not covariant, on their parameter types.
#matt is completely right about why this works – it's due to the fact that method inputs are contravariant, instead of covariant.
This means that you can only override a given function with another function that has a broader (or the same) input type – meaning that you can substitute in a superclass argument in place of a subclass argument. This may seem completely backwards at first – but it makes total sense if you think about it a bit.
The way I would explain it in your situation is with a slightly stripped down version of your code:
class ParameterClassA {}
class ParameterClassB: ParameterClassA {}
class WorkingClassA {
func someFunction(param: ParameterClassB) {}
}
class WorkingClassB: WorkingClassA {
override func someFunction(param: ParameterClassA) {}
}
Note here that WorkingClassB is overriding the someFunction with ParameterClassA – the superclass of ParameterClassB.
Now, imagine you have an instance of WorkingClassA, and then call someFunction on this instance, with an instance of ParameterClassB:
let workingInstanceA = WorkingClassA()
workingInstanceA.someFunction(ParameterClassB()) // expects ParameterClassB
So far, nothing unusual. We passed a ParameterClassB instance to a function that expects a ParameterClassB.
Now let's assume that you swap your WorkingClassA instance with a WorkingClassB instance. This is perfectly legal in OOP – as the subclass can do everything the superclass could do.
let workingInstanceB = WorkingClassB()
workingInstanceB.someFunction(ParameterClassB()) // expects ParameterClassA
So what happens now? We're still passing a ParameterClassB instance into the function. However, now the function expects a ParameterClassA instance. Passing a subclass into an argument that expects a superclass is legal in OOP (this is covariance), as the subclass can do everything that the superclass could do – therefore this doesn't break anything.
Because the function signature can only get more broad (or remain unchanged) as you override it, it ensures that you can always pass it the original argument type that the function defined, as any superclass argument in an overridden version can accept it.
If you think about the reverse for a second, you'll see why it couldn't possibly work. As the function would get more restrictive as it gets overridden, it won't be able to accept arguments that the superclass could originally accept – therefore it cannot work.
Coming from a C++ background (templates), I'm struggling to understand why the following piece of Swift code (generics) does not compile:
func backwards<T>(array: [T]) -> [T] {
let reversedCollection = array.sort(>)
return reversedCollection
}
The way I understand it is that T is a generic type on which I do not put any constraint (<T>) and declare array to be of type Array<T>. Yet this produces the following error:
Ambiguous reference to member 'sort()'
I understand that constraints can be put on the generic type using protocols. However, in this case, I don't want any constraint on T. Rather, I want to constrain the type of the first parameter.
I've been reading Apple's documentation on Generic Types for a few hours now but I'm still not much wiser. I have the impression that this is not possible and constraints are put solely on the declared types, but that's as far as I got.
So the question is: If possible, how do I put constraints on the types of the function arguments? If not, how do I achieve the same result?
sort(>) is legal only when Element is Comparable. Since the element type of [T] is T, T must conform to Comparable in order for the array [T] to be sortable via >:
func backwards<T: Comparable>(array: [T]) -> [T] {
let reversedCollection = array.sort(>)
return reversedCollection
}