Looking back at the classics: Generic data types in Swift (Part 1)

Looking back at the classics: Generic data types in Swift (Part 1)

Many iOS developers start their pilgrimage to Swift by wondering about one thing: what are the main differences between Swift and Objective-C? Is it different enough to make the effort of switching languages worth it? Of course, there are notable differences between both languages. For those with experience developing in the “old days” of iOS/MacOS, maybe the most influent, although not the most visible, is the complete walkout from the dynamic nature of Objective-C.

In the Objective-C world, we lived in a happy and comfy but dangerous anarchy. We could use a variable to hold an array, and later use it to hold a dictionary by using the id type. Methods could take any type of data as parameters, and we could even create classes while in run-time. “Imagine no type restrictions,” John Lennon would’ve sung. It sounds great at first but, as Spiderman learned the hard way, “With great power comes great responsibility.” In our case, it meant that our code was plagued with data type checks to avoid exceptions and crashes.

Swift has gone in the opposite direction by breaking this dynamic nature apart. That means that every data type in our code is checked, making our code safer and cleaner. Not having a plethora of if-else checks is always a nice thing. But what about those good old methods that could take anything? What about creating or analyzing classes and methods in run-time? Well…we lose the latter, but Swift makes up for the former by giving us generics.

Generic coding has been around since the 1970s, and it’s a powerful tool to create sets of algorithms and data types that can work with anything. In short, by using generics we’re assigning nicknames to our future data types so we can handle them without caring what they will be when our code gets executed. i.e. take this simple example from the Apple documentation:

func swapTwoValues<«T»>(inout a: T, inout b: T) {
       let temporaryA = a
       a = b
       b = temporaryA
}
»Generic type|This <code><T></code> specifier lets you assign a nickname to the types you're going to use in your function. In our case we only are working with just one type, but you can work with several and even apply restrictions to some of them.«

As you can see, this function gets two variables and swaps their values. So the values both variables take doesn’t matter as long as they take the same kind of data. So we tell them that their type is going to be “T”, which will stand for Array, String, Dictionary . . . whatever they’ll be in the actual code. A piece of cake, huh? Well, not always. Maybe you want your variables to take different types, and some of them should conform to a certain protocol? That can get a bit harder and sometimes this makes the syntax a little cumbersome. But it’s a powerful tool in our belts nonetheless.

And how do we use this function? In the two examples below, T is automatically inferred to be an Int and a String respectively:

var someInt = 1
var anotherInt = 47
«swapTwoValues(&someInt, &anotherInt)»

var someString = "foo"
var anotherString = "bar"
«swapTwoValues(&someString, &anotherString)»
»Generic swap method|<code>someInt</code> now contains 47, and <code>anotherInt</code> contains 1«
»Generic swap method|<code>someString</code> is now <code>bar</code>, and <code>anotherString</code> is now <code>foo</code>.«

One cool thing we can do with generics is to create new collection types that can hold any type of data. Actual Swift types like arrays and dictionaries are made using generics, and nothing should stop us from doing the same! So inspired by this great power, and its corresponding great responsibility, we’re starting this series of posts to help other developers with generic development. We’ll also take a look back at some classic collection types (well, the final one won’t be so “old” ;-)). Yes, we’re talking about the same ones you hated studying in your college days: singly-linked lists, stacks, etc. But it gets cooler than that, trust us :-).

(You can find the source code used in this article in the following Playground file)

Singly-linked list

Linked lists hit the ripe-old age of 60 in 2015. They’ve been around before the inception of the C language and personal computers. In fact, integrated circuits were still at the stages of infancy back then! So you can imagine, that there is at least one implementation of this abstract data type in almost every programming language in existence. So what are linked lists? In short, they’re a collection of nodes, each one being composed of a value and a pointer to the next node. It’s like having a group of kids holding hands with a certain value printed on their T-shirts.

To create a list, you start with an empty list (no nodes) and then prepend values by connecting the pointers of the last node with the new one. What if you want to get a certain value from the list? You start from the beginning and then run through each node until you find what you need. That means that adding new data is a piece of cake while reading a random value is a little bit more costly. Also, take into account that singly-linked lists’ nodes can only point to the next node, you can’t go backward. Even with those “limitations”, this simple data type has become the foundation stone for many others. So let’s get our hands dirty and create our own generic singly-linked list in Swift!

class List<T> {
    let head : T?
    let tail : List<T>?
    let count : Int

    init(head: T?, tail: List<T>?) {
        self.head = head
        self.tail = tail

        «switch (head, tail)» {
        case let (.Some(h), .Some(l)): count = l.count + 1
        case let (.Some(h), nil): count = 1
        case let (nil, .Some(l)): count = l.count
        default: count = 0
        }
    }

    convenience init() {
        self.init(head: nil, tail: nil)
    }
}
»Pattern matching|When initializing a new list from another, we're using pattern matching to handle several situations we could encounter, so we can calculate the right number of elements.«

The first version of the list can hold a head or the “newest” element, and a tail which is the rest of the list to which the head is attached to. It also provides a convenient integer to count how many elements it contains. Then we have two initializers: one to create an empty list and another to create a list with a certain head and a certain tail. Every node in the list will hold a value of type T, that in run-time will get changed to Int, Array, Dictionary, or any custom class. There are no limits to what our lists are capable of holding!

We can also make it a little more complete by implementing some methods to add an item, or to advance through each node:

extension List {
    func cons(item: T) -> List {
        return «List(head: item, tail: self)»
    }

    func next() -> List<T>? {
        return tail
    }
}
»List construction|Appending a new item to a list is the same as creating a new list. Its head will be the new element and the tail the previous one. We're just chaining elements to lists and passing them around.«

Now we can add stuff to our list by using the cons method, and also run through its elements by using the next method. As you can see, every method in our implementation returns another List, or an option of a List if we’re not sure if we’ll get a value. This guarantees immutability which always makes things safer. Now we have a perfectly working singly-linked list we can use!

var list = «List»<Int>()
list = «list.cons(1)»
list = «list.cons(2)»
list = «list.cons(3)»
list = «list.cons(4)»
»List construction|<code>list</code> is empty for now. Notice how in the next runs we're assigning the results back to the variable (our lists are immutable) and how each new value prepends the others.«
»List construction|<code>list</code> now contains [1].«
»List construction|<code>list</code> now contains [2, 1]«
»List construction|<code>list</code> now contains [3, 2, 1]«
»List construction|<code>list</code> now contains [4, 3, 2, 1]«

To use our brand-new List, we create an empty one and start prepending values to it. Take a look on how we initialize it by using the **List** type. That’s our way to specify the generic type, and to Swift it means, "I want a List that holds Int values." Then the **T** in our implementation will become **Int** and the code will work the same as if it was Float or String. We could make our implementation even more complete, but think about what we’ve got so far: we’ve implemented a linked list than can hold ANYTHING in such a short amount of code!

Remember that you can find the source code for this implementation in the following Playground file, in case you want to go deeper!

In our upcoming posts, we’ll continue to explore other simple abstract data types from our history, and how we can use generics to implement them easily in Swift. In the meantime, if you have any questions feel free to get in touch with us on Facebook, Twitter, or in the comments below. Happy coding!

blog comments powered by Disqus

Ensure the success of your project

47 Degrees can work with you to help manage the risks of technology evolution, develop a team of top-tier engaged developers, improve productivity, lower maintenance cost, increase hardware utilization, and improve product quality; all while using the best technologies.