Answers to What Are Generics, How and When to Use Them
Normal programming is a technique for writing reusable code while maintaining type safety. This is a type of polymorphism known as “parametric polymorphism”. Polymorphism means using a single interface or name that works with multiple types.
There are mainly three types of polymorphism:
- Ad hoc polymorphism. We can define multiple functions with the same name, but different types of functions is called ad hoc polymorphism.
- Subtype polymorphism. If a function expects a class C, we can also pass in a subclass of C. This is called subtype polymorphism.
- Parametric polymorphism. If a function has a generic parameter, we can pass it to different types. This is called parametric polymorphism.
One reason for this is less code. instead of writing this:
func square(value: Int) -> Int…
func square(value: Double) -> Double …
func square(value: Float) -> Float …
We can write this:
func square(value: T) -> T …
This way, we can write a single function instead of writing multiple functions to handle different parameter types (function overloading). Here T
stands for type. You can put anything in there; it doesn’t have to be T
, and it doesn’t matter what we put in. This function can also be defined as:
func square(value: PutAnythingHere) -> PutAnythingHere …
This means that if you pass an object with type x
For this function, it will return an object with type x
,
func square(value: Any) -> Any
This is not good and won’t work because we can pass any type and return any type, which doesn’t make any sense.
We use generics to express and limit what types a class can work with. For example, when we declare an array, we declare it like this:
var arr: Array
arr = [1,2,3,4,5]
it means that arr
is an array of integers. This way, we can’t insert anything but integers into this array. We can define an array of integers because Array has a generic type defined:
public struct Array …
If you really want a heterogeneous array that can hold any number of objects, you can define it as:
var arr: Array
arr = [1, “Two”, 5.8, Car(), Calendar(), OperationQueue()]
This is something you should avoid unless you really know what you are doing as it can open a can of worms. The reason for this is that if you want to access the elements in the array, you have to open them like this:
var obj0 = arr[0] as! Int
var obj1 = arr[1] as! String
var obj2 = arr[2] as! Double
var obj3 = arr[3] as! Car
var obj4 = arr[4] as! Calendar
var obj5 = arr[5] as! OperationQueue
You see how problematic and dangerous this is. It looks like your app may crash at any time. Can crash with simple code like this:
var obj0 = arr[0] as! String
error: Execution was interrupted, reason: signal SIGABRT.
Could not cast value of type ‘Swift.Int’ (0x1b9ac80b8)
to ‘Swift.String’ (0x1b9ac6258).
So, stick with a generic type when you create an array.
Our SQUARE function above is problematic because we can pass any number of types to it. what if we pass a string?
func square(value: T) -> T …
square(“Some string here”)
Strings don’t have classes, so it doesn’t make sense. We need to limit our generic to a numeric type like this:
func square(value: T) -> T …
This way, we can only pass values that conform to the numeric protocol. We know that all numeric values are squares, so our function has correct bounds for now, and one cannot pass something that doesn’t make sense. If we try to mess with this by calling:
square(“Some string here”)
The compiler gives this error below and does not allow this to happen:
function ‘square(value:)’ requires that ‘String’ conform to ‘Numeric’
We restricted generic types by restricting them to conforming to a single protocol. We can also use a class that will limit the generic type to a subclass from a specific class.
class SomeClass
var name: String = “class name”
func printClassName(value: T)
print(value.name)
class SomeSubClass: SomeClass
printClassName(value: SomeSubClass())
//prints:
//class name
Functions and methods are not just generic types. We can also have generic structures, classes and enums.
Example 1
Let’s create a stack structure that works with a generic type. we need to define this stack with the type of our name Object
so only we can push
, pop
And peek
same type.
struct Stack
Here we have defined a structure, which must have a predefined object type. For example, the stack of functions would be like this:
typealias Work = () -> Void
var stack: Stack = Stack()
stack.push …
Here, we limit our stack to the stack of Work
works. We can only push functions onto this stack, specially typed functions Work
which doesn’t receive any arguments and doesn’t return anything. We can call functions in this stack like this:
while let work = stack.pop()
work()
Example 2
Let’s make a SwiftUI View
who receives the second View
as a subview. View
SwiftUI has a protocol, so we need to define a structure that conforms to it View
protocol and passes a subview as a generic type that also conforms to View
Drafting
struct SomeView: View
let subview: Content
var body: some View
subview
we can make an example of SomeView
passing a Text
or one Image
as a subview, because both Text
And Image
according to View
protocol, like this:
let someView = SomeView(subview: Text("some text…"))
let anotherView = SomeView(subview: Image("some_image"))
Example 3
here is the definition Optional
In Swift:
enum Optional
case none
case some(Wrapped)
Optional
is a common type. When we choose a value for Wrapped, we get a concrete type. For example, optional
When we want to convert a generic type to a concrete type, we must choose exactly one concrete type for each generic parameter.
Generics are used in the Swift standard library. For example:
- Collections like Array, Set and Dictionary.
Optional
Uses a generic parameter to abstract over its wrapped type.Result
There are two generic parameters – one representing a successful value and the other representing an error.Unsafe[Mutable]Pointer
It is generic on the type of memory it points to.Key
Paths are generic on both their root type and the resulting value type.
Associated type is a placeholder name for the type used as part of the protocol. The actual type for the type concerned is not specified until the protocol is adopted. It is a generic type that is declared in a protocol. This way we can use generics in protocols.
what if we want to define our Stack
Protocol instead of Structure? let’s try. If we write the line below:
protocol Stack
Compiler gives this error and tells us how to use generics with protocols. Here’s the message:
An associated type named ‘Object’ must be declared in the protocol ‘Stack’
or a protocol it inherits
So, how can we define our stack as a protocol? Like this:
protocol Stack
associatedtype Objectmutating func push(object: Object)
mutating func pop() -> Object?
func peek() -> Object?
The only difference is, instead of defining protocol Stack
we defined it protocol Stack associatedtype Object
, Now, let’s make a Stack
structure that conforms to Stack
Drafting
struct WorkStack: Stack
typealias Object = Work
private var objects: [Work] = []mutating func push(object: @escaping Work)
objects.append(object)
mutating func pop() -> Work?
return objects.popLast()
func peek() -> Work?
return objects.last
In WorkStack
We can remove the typealias line because the compiler can infer the associated type from the function declarations and understand that the associated type is Work
, So, we can define it like this:
struct WorkStack: Stack
private var objects: [Work] = []mutating func push(object: @escaping Work)
objects.append(object)
mutating func pop() -> Work?
return objects.popLast()
func peek() -> Work?
return objects.last
//We can create an instance of WorkStack and use it like this:
var workStack = WorkStack()
workStack.push …
while let work = workStack.pop()
work()
We can make other stacks corresponding to Stack
protocols like IntStack
, StringStack
, CarStack
, etc., like how we did above. But what if we want to create a generic stack that conforms to Stack
Protocols that can be any specific type of stack? We can get it like this:
struct MyStack- : Stack
private var objects: [Item] = []mutating func push(object: Item)
objects.append(object)
mutating func pop() -> Item?
return objects.popLast()
func peek() -> Item?
return objects.last
//With MyStack, we can create a stack of any type.
var carStack = MyStack()
carStack.push(Car())
This is great Now we can make any type of stack we want. What if we extend an existing class to conform to a related type of protocol? For example, expand the array to conform Stack
Drafting Here’s how we can do it:
extension Array: Stack
mutating func push(object: Element)
self.append(object)
mutating func pop() -> Element?
return self.popLast()
func peek() -> Element?
return self.last
Above, we are using generic type Element
of array as related type Object
Of Stack
Drafting the compiler infers that Array.Element == Stack.Object
, remember how Array
And Stack
It is declared:
public struct Array …
protocol Stack
associatedtype Object
…
what if we pass stack Work
Example for a function? What happens if we write the below code? (Keyword inout
is to modify the stack that we pass, not related to our topic).
func executeStack(stack: inout Stack)
...
We get this error:
Use of protocol ‘Stack’ as a type must be written ‘any Stack’
here we can use any
either some
Keyword. The difference between the two is another matter. if we write this some Stack
it means something that conforms to protocol Stack
, Let’s try calling the functions like this:
func executeStack(stack: inout some Stack)
while let work = stack.pop()
work()
For the above code, the compiler gives this error:
Cannot call value of non-function type ‘(some Stack).Object’
This makes a lot of sense because the compiler doesn’t know that the items in this stack are ‘functions’ that it can call. To make it clear to the compiler, we need to declare it like this:
func executeStack(stack: inout T) where T.Object == Work
while let work = stack.pop()
work()
Above, we are telling the compiler that the parameter stack corresponds to Stack
protocol, where its Object
is an example of Work
, It works, but it’s a bit ugly. We can also define it as (with a condition):
func executeStack(stack: inout some Stack)
while let work = stack.pop()
work()
condition is that we need to declare our Stack
The primary associated type of protocol, such as:
protocol Stack
above, Stack
tells the compiler Object
is the primary associated type of Stack
Drafting
Suppose we want to merge two stacks. Here’s how we can declare a function that does this:
func merge- (stack1: some Stack
- ,
stack2: some Stack- ) -> some Stack
-
…
return mergedStack
We have used a WHERE clause in the example above, which is unnecessary as we have a better option of using a primary associated type, where clause is required for the relevant extension.
Let’s say we want to write an extension for our MyStack
To add functionality to encode itself. Maybe we want to save it to disk. To encode, the stack and its items must be consistent Encodable
Drafting Here’s how we define it:
extension MyStack: Encodable where Item: Encodable
func encode() -> Data?
try? JSONEncoder().encode(self)
As you can see, we used where
clause to limit the type concerned to being encodable. So, this extension only applies to MyStack
Instances in which they and their items are encodable. For all others, this extension does not exist.
For example, because String
conforms to Encodable
protocol, we can use it like this:
var stringStack = MyStack()
stringStack.push(object: “a string”)
print(stringStack.encode())
prints: Optional(20 bytes)
Sometimes, instead of conforming to a protocol, we may need a specific type of corresponding type. For example, if we want to write an extension only for heaps that have Work
As an associated type, we can declare something like this:
extension MyStack where Item == Work
mutating func execute()
while let work = self.pop()
work()
Starting with Swift 5.7, it is also possible to write this using angle brackets:
extension MyStack
mutating func execute()
while let work = self.pop()
work()
We can put many restrictions with where clause. Something like this:
extension Foo where T: Sequence, T.Element == Character
func specialCaseFoo() …
Generic typeleasis allows you to provide names for existing nominal generic types or non-nominal types (for example, tuples, functions, etc.) with generic parameters.
typealias StringDictionary = Dictionary
typealias DictionaryOfStrings = Dictionary
typealias IntFunction = (T) -> Int
typealias Vec3 = (T, T, T)
typealias BackwardTriple = (T3, T2, T1)
you can see This Video for implementation details of generics in Swift.