Tuesday, October 24, 2017

Why you should learn Generics


Over the past couple of years, I have been surprised with the number of senior level developers that I have meet that do not use Generics.  Not only do they not use generics but they actually have very little understanding of them.  In this article, I would like to explain why all developers should not only learn generics but use them regularly.  While this article focuses on the Swift programming language, the concepts of why you should learn and use generics will apply to almost any language that have generics similar to Swift.

I will start off by demonstrating the problem that generics solve.  Let’s say we are developing a new application and within this application we have the need to swap the value of two integer variables.  To meet this need, in Swift, we could very easily write a function like this:

func swapInt(_ a: inout Int, _ b: inout Int) {
    let tmp = a
    a = b
    b = tmp
}

This function could then be used like this:

var one = 1
var two = 2
swapInt(&one, &two)

Now, a few days or weeks later, as we continue to write the application, we discover a need to swap the values of two string variables.  We could then add another function to do this.  The following code shows this new function:

func swapInt(_ a: inout String, _ b: inout String) {
    let tmp = a
    a = b
    b = tmp
}

As time goes by we may end up with several functions like the previous swap functions but with different parameter types.  Instead of having all of these different functions, in the beginning we could have created one generic swap function that looked like this:'

func swapGeneric<T>(_ a: inout T, _ b: inout T) {
    let tmp = a
    a = b
    b = tmp
}

Notice that the generic version of the swap function uses a placeholder, capital T, rather than the actual parameter type.  This placeholder tells the compiler that we will define the type to use at runtime.  Since the T placeholder is used for both parameters, they are required to be of the same type.

We would use this generic function exactly like we would use the non-generic swap functions.  The following code illustrates this:

var one = 1
var two = 2
swapGeneric(&one, &two)

Now the parameters can be instances of any type as long as they are instances of the same type.  We could swap the values of two string types like this:

var one = “one”
var two = “two”
swapGeneric(&one, &two)

Swapping values isn’t too exciting so let’s look at something that does a bit more by seeing how we could use generics to create a very basic queue type.  The following code shows how to do this.

struct Queue<T> {
    private var items = [T]()
    
    public mutating func push(_ item: T) {
        items.append(item)
    }
    public mutating func pop() -> T? {
        if (items.count > 0) {
            return items.remove(at: 0)
        } else {
            return nil
        }
    }
}

We would use this queue type like this:

var queue = Queue<Int>()
queue.push(1)
queue.push(2)
print(queue.pop())
print(queue.pop())

One of the best things about this queue type is it is ready to accept any type.  We could very easily create an instance of the Queue type that would store String values like this:

var queue = Queue<String>()

One of the questions that someone new to generics may ask is: Why don’t we just use the Any type rather than generics?  In Swift, the Any type allows us to use instances of any type.  The following code shows how we could create a Queue type that used the Any type:

struct QueueAny {
    private var items = [Any]()
    
    public mutating func push(_ item: Any) {
        items.append(item)
    }
    public mutating func pop() -> Any? {
        if (items.count > 0) {
            return items.remove(at: 0)
        } else {
            return nil
        }
    }
}

The QueueAny type lets us create a queue that can contain any type similar to the previous generic queue however there are several disadvantages to this method.  The one disadvantage that we will focus on here is the ability to add any type to the queue.  For example this code would be perfect ok with the QueueAny queue:

var queueAny = QueueAny()
queueAny.push(2)
queueAny.push("Hi")

Notice how we are able to add both an integer and a string to the same queue.  The QueueAny type functions similar to how Arrays worked in Objective-C.  Now if we popped an item off the queue we would need to typecast it before we used any of the methods or properties of the type.  This does not allow us to have any compile time checks to make sure we are using the correct types in our queue and is prone to error, as any Objective-C developer can tell you.

With the generic Queue type, since we explicitly define the type of items stored in the queue, we are able to use the properties and methods of that type without the need to typecast.  This gives us the compile time checks that ensures we are using the correct types.

The answer to the question about why you should learn generics is:  Generics enable us to write very flexible and reusable code that is also very safe.  In this article we only scratched the surface of generics.  If you are unfamiliar with generics I would recommend taking the time to learn more about them.

In my Mastering Swift 4 book there is a chapter dedicated to generics that gives the reader a good introduction to generics which shows how to create generic functions, types and protocols.  We also look at using type constraints with generics and the new generic subscripting feature that was introduced in Swift 4.

In my Swift 4 Protocol Oriented Programmingbook we also have a chapter dedicated to generics.  In that chapter, we briefly cover generic types, protocols, type constraints and generic subscripting.  We also include more advance topics like how generics are used in the Swift standard library and how to use generics to implement the Copy-on-Write feature for custom value types.
  

No comments:

Post a Comment