Showing posts with label generics. Show all posts
Showing posts with label generics. Show all posts

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.
  

Saturday, April 11, 2015

Generics Functions in Swift

For developers that have used the Java or C# programming languages, Generics in Swift should look pretty familiar because Swift’s implementation is very similar to those languages.  For other developers Generics may seem a bit foreign at first.

Before we look at how to use Generics in Swift, let’s take a look at the problem that they are designed to solve.  Lets say that we wanted to write a function that swapped the values of two Int types.  We could very easily write the function like this:

func swapInts (inout x: Int, inout y: Int) {
    let tmp = x
    x = y
    y = tmp
}

Now lets say that we also have a need to swap the values of two String types.  We could then write another function like this:

func swapString (inout x: String, inout y: String) {
    let tmp = x
    x = y
    y = tmp
}

Having two functions like this isn’t too bad but what if we also had a need two swap two UInt, Double and Boolean types as well?  This could get out of hand very quickly.   The worse part is we are duplicating most of the code each time we add a new swap function.  This is where Generics can help us.

Generics allow us to write flexible and reusable code that avoids the duplication that we saw in the previous examples.  Now lets look at how we could write one function with Generics that would allow us to swap the values of any data type. 

func swapGeneric<T> (inout x: T, inout y: T) {
    let tmp = x
    x = y
    y = tmp
}

The swapGeneric() function looks pretty similar to the other two swap functions that we saw earlier except for the capital T.  The capital T is used as a placeholder and lets Swift know that we will be defining the type, associated with the placeholder, at run time.  When we define a Generic function we include the placeholder between two angled brackets (<>).  We can then use that placeholder in place of any type definition within the function, the function’s definition or as the return type of the function.

There is nothing special about the capital T that we used in our example.  We can actually use any valid identifier but generally we use a capital T or a capital E.
We would use the genericSwap() function like this:

var a = 1.1
var b = 2.2
swapGeneric(&a, &b)
println("a = \(a)  b = \(b)")


var c = "One"
var d = "two"
swapGeneric (&c, &d)
println("c = \(c) d = \(d)")

In these examples, we see that we can swap two Double types or two String types with the same swapGeneric() function.  We could expanded these examples to include any data type that we want.
We are also not limited to using just a single generic type.  Lets look at an example of where we use two generic types.

func printValue<T,E> (value1: T, value2: E) {

    println("value1:  \(value1)")
    println("value2:  \(value2)")
}

In this function we have two generic types defined, the T and the E.  Here are a couple of examples of how we could use this function.

printValue(“hello”, 1234)
printValue(3.1415, “bye”)

We can see that we can include different data types within the parameters that we are sending to the printValue() function.

In this post, we briefly looked at how we could use Generics within a function definition to create a function whose parameter types can change at runtime as needed.  This is one of the most popular uses for Generics and is also the basis of Swift’s collection types (Array, Dictionary).  For a more in-depth discussion of Generics and how to create Generic and Associated types, check out my book Mastering Swift published by Packt Publishing.