Generics

This article is meant for students who already have an understanding of parametric polymorphism. A very brief and lacking explanation of generics is that we want when we are working with a certain type, we are going abstract out a second (or more) type that will not be decided until later. Until they are chosen, we give them a placeholder name and work with them in the abstract.

In Swift, we denote polymorphism by enclosing a type, or a set of types within <> after the name of the new type we are declaring. We can specify requirements for our types, conformance to a protocol or being a (sub)class, through constraints.

Constraints

Now that we have notation down (at least conceptually) what can we apply generics to? Almost everything: classes, enums with associated values, structs, functions and typealiased tuples!

Learn by Example

We are going to implement linked lists for this section. Linked Lists are another form of list, but instead of storing our elements by placing them in adjacent places in memory, we create nodes that have their value and a pointer to the next value. It helps with the runtime in certain cases, but you don't really need to understand that for this section.

As we are building a list, it makes sense to use generics because we will want to create lists with all kinds of Elements.

Normally, I like thinking about Linked Lists as function entities and structs are perfect for that. But sadly, structs do not support recursion. So, I will be using classes.

As I said before, the parametrizing type here is the type of the element, so I will name the type Element:

class LinkedList<Element> {
    
    var value: Element
    var next: LinkedList? // The Element type here is preserved, 
    // so all nodes in this section have the same element type
    // aka it is implied that next: LinkedList<Element>?
    
    init(_ value: Element, _ next: LinkedList? = nil) {
        self.value = value
        self.next = next
    }
    
}

We can create a new list of integers with the code:

var integerList = LinkedList(3, LinkedList(4, nil))

Notice that we don't need to specify a type–it is inferred.

Now I have an integer list... What if I want to convert it to a float list? Hopefully, you know what map is and anonymous functions. The lowdown is that we are passing a function, which maps from one type to another and applying it to everything in our list. For more on map, and anonymous functions, review closures in the functions page and advanced array operations.

FunctionsAdvanced Array Operations

This function is a little bit spicy because we are going to need a second associated type! The return list type will be new and we want to be generic.

func map<NewElement>(_ transform: (Element) -> NewElement) -> LinkedList<NewElement> {
    let rest = next?.map(transform)
    return LinkedList<NewElement>(transform(value), rest)
}

I don't think the NewElement is required in the return statement, but my playgrounds were being buggy, so ¯_(ツ)_/¯.

Other Resources

We also have a great example of a generic with our discussion of Dictionary Implementations:

Dictionary Implementation

Tuples are bit strange, so we moved that discussion to its own section.

Tuples

Protocols also have their own version of polymorphism, but that is another entire can of worms:

Protocols With Associated Types

Last updated