Generic programming is a way to write functions and data types without being specific about the types they use. Arrays are a good example – whether you have an array of strings or an array of integers or an array of AnyObject
, they’re all arrays that work in the same way.
The difference is the type of thing they store, but there’s still just a single Array
type. The big idea here is that you say what the array will hold when you declare it. In a way, it’s like having a type become an additional parameter to a function, saving you from writing many different versions of a similar function or data type.
You’ll find generics in use throughout Swift, which makes understanding them essential to a complete understanding of the language. In this tutorial, you’ll experiment in a Swift playground to learn what generics are and why they’re useful, how to write generic functions and data structures, as well as more intermediate tasks such as how to use type constraints and extend generic types. Finally, you’ll take a look at some of the new generics features in Swift 2.
Getting Started
Begin by creating a new playground: In Xcode, go to File\New\Playground…, name the playground Generics and select OS X as the platform. Click Next to choose where you’d like to save your new playground and finally, click Create.
Suppose your application requires a function to add two integers. Add the following to your newly created playground:
func adderInt(x: Int, _ y: Int) -> Int { return x + y } |
adderInt(_:_:)
takes two Int
values and returns their sum. You can give it a try by adding the following code to the playground:
let intSum = adderInt(1, 2) |
This is a pretty simple example that also demonstrates Swift’s type safety. You can call this function with two integers, but not any other type. Now suppose you also need to add two Double
values. You would create a second function adderDouble
:
func adderDouble(x: Double, _ y: Double) -> Double { return x + y } let doubleSum = adderDouble(1.0, 2.0) |
The function signatures of adderInt
and adderDouble
are different, but the function bodies are identical. Not only do you have two functions, you’ve also repeated the code inside them. Soon you’ll see how you can use generics to reduce these two functions to one and remove the redundant code.
Arrays as Generic Types
Add the following to your playground:
let numbers = [1, 2, 3] let firstNumber = numbers[0] |
Here, you create a simple array of three numbers and then take the first number out of that array.
Now Option-click first on numbers
and then on firstNumber
. What do you see? Because Swift has type inference, you don’t have to explicitly define the types of your constants, but they both have an exact type: numbers
is an [Int]
—that is, an array of integers—and firstNumber
is an Int
.
The Swift Array
type is a generic type, or one that requires a type parameter in order to be fully specified. Via type inference and also thanks to Swift’s type safety, numbers
can only contain Int
values, and when you take anything out of that array, Swift—and more importantly you—both know it must be an Int
.
You can better see the generic nature of Array
by forgoing type inference and the Swift array syntactic sugar and adding the following code to the end of the playground:
var numbersAgain = Array<Int>() numbersAgain.append(1) numbersAgain.append(2) numbersAgain.append(3) let firstNumberAgain = numbersAgain[0] |
Check the types of numbersAgain
and firstNumberAgain
by Option-clicking on them; the types will be exactly the same as the previous values. Here you specify the type of numbersAgain
using explicit generic syntax, by putting Int
in angle brackets after Array
.
Try appending something else to the array, like a Double
or a String
:
numbersAgain.append("hello") |
You’ll get an error—something like, Cannot invoke 'append' with an argument list of type '(String)'
. The compiler is telling you that you can’t add a string to an array of integers. As a method on the generic type Array
, append
is a so-called generic method. It knows the type of the containing array’s elements, and won’t let you add something of an incorrect type.
Other Generic Types
In Swift, some of the most common structures you use are generic types. You took a quick look at Array
, above. Dictionaries and optionals are also generic types and result in type-safe data structures.
Create the following dictionary of country codes at the end of your playground and then look up the country code of Austria:
let countryCodes = ["Austria": "AT", "United States of America": "US", "Turkey": "TR"] let at = countryCodes["Austria"] |
Check the types of both declarations. You’ll see that countryCodes
is a dictionary of String
keys and String
values—nothing else can ever be in this dictionary. The formal generic type is Dictionary
.
The type of at
is String?
, which is shorthand for Optional
, so you can see that optional types are generic types. Generics are all over the place! The compiler enforces that you can only look up string keys and you always get string values back. The string value is optional, because there might not be a value corresponding to the passed-in key.
Add the following to your playground to see the full explicit syntax for creating an optional string:
let optionalName = Optional<String>.Some("John") if let name = optionalName { } |
Check the type of name
, which you’ll see is String
. Optional binding, that is, the if-let
construct, is a generic operator of sorts. It takes a value of type T?
and gives you a value of type T
.
These are the main ideas behind generics. For generic functions, the parameter list and/or the return value depend on one or more generic types, such as T
. You specify those types on an individual basis when you call the function, either explicitly or via type inference.
Now that you grasp of the basics of generics, you can learn about writing your own generic functions and types!
Writing a Generic Function
Add the following function to the bottom of the playground:
func pairsFromDictionary<KeyType, ValueType>(dictionary: [KeyType: ValueType]) -> [(KeyType, ValueType)] { return Array(dictionary) } |
Take a good look at the function declaration, parameter list and return type.
The function is generic over two types that you’ve named KeyType
and ValueType
. The only parameter is a dictionary from KeyType
to ValueType
, and the return value is an array of tuples of the form—you guessed it— (KeyType, ValueType)
.
You can use pairsFromDictionary
on any valid dictionary and it will work, thanks to generics:
let pairs = pairsFromDictionary(["minimum": 199, "maximum": 299]) let morePairs = pairsFromDictionary([1: "Swift", 2: "Generics", 3: "Rule"]) |
Of course, since you can’t control the order in which the dictionary items go into the array, you may see an order of tuple values in your playground more like “Generics”, “Rule”, “Swift”, and indeed, they kind of do! :]
The compiler generates separate functions for each possible KeyType
and ValueType
, filling in the real types in the function declaration and body. In the first case of pairs
, pairsFromDictionary
returns an array of (String, Int)
tuples, and in the second case of morePairs
, it returns an array of (Int, String)
tuples. At the code level, you created both of these using a single function.
Writing a Generic Data Structure
A queue is a data structure kind of like a list or a stack, but you can only add new values to the end (enqueuing) and only take values from the front (dequeuing). This concept might be familiar if you’ve ever used NSOperationQueue
or Grand Central Dispatch, with dispatch_async
and friends.
Add the following struct
declaration to the end of your playground:
struct Queue<Element> { } |
Queue
is a generic type over Element
. Queue
and Queue
, for example, will become types of their own, generated by the compiler, that can only enqueue and dequeue strings and integers, respectively.
Add the following private property to the queue:
private var elements = [Element]() |
You’ll use this array to hold the elements, which you initialize as an empty array. Note that you can use Element
as if it’s a real type, even though it’ll be filled in later.
Finally, implement the two main queue methods:
mutating func enqueue(newElement: Element) { elements.append(newElement) } mutating func dequeue() -> Element? { guard !elements.isEmpty else { return nil } return elements.removeAtIndex(0) } |
Again, the type parameter Element
is available everywhere in the struct
body including inside methods. Making a type generic is like making every one of its methods implicitly generic over the same type. You’ve implemented a type-safe generic data structure, just like the ones in the standard library.
Play around with your new data structure for a bit at the bottom of the playground.
var q = Queue<Int>() q.enqueue(4) q.enqueue(2) q.dequeue() q.dequeue() q.dequeue() q.dequeue() |
Also, have some fun by intentionally making as many mistakes as you can to trigger the different error messages related to generics—for example, add a string to your queue. The more you know about these errors now, the easier it will be to recognize and deal with them later.
Intermediate Topics
Now that you know the basics of creating and working with generic types and functions, it’s time to move on to some more advanced features. You’ve already seen how useful generics are to limit things by type, but you can add additional constraints as well as extend your generic types to make them even more useful.
Type Constraints and Where Clauses
The function below sorts an array and finds the middle value. Add it to your playground:
func mid<T>(array: [T]) -> T { return array.sort()[(array.count - 1) / 2] } |
You’ll get an error. The problem is that for sort()
to work, the elements of the array need to be Comparable
. You need to somehow tell Swift that mid
can take any array as long as the element type implements Comparable
.
Change the function declaration to the following:
func mid<T where T: Comparable>(array: [T]) -> T { return array.sort()[(array.count - 1) / 2] } |
Here, you use the where
keyword to add a type constraint to the generic type parameter T
. You can now only call the function with an array of comparable elements, so that sort()
will always work! Try out the constrained function by adding:
mid([3, 5, 1, 2, 4]) // 3 |
In the case that where
makes the type conform to a protocol, such as above, you can use the following syntactic sugar and remove where
:
func mid<T: Comparable>(array: [T]) -> T { return array.sort()[(array.count - 1) / 2] } |
Other uses of where
include when there is more than one generic type, and you want to add a constraint between two or more of them.
Now that you know about type constraints, you can create a generic version of the adder functions from the beginning of the playground. Add the following protocal and extensions to your playground:
protocol Summable { func +(lhs: Self, rhs: Self) -> Self } extension Int: Summable {} extension Double: Summable {} |
First, you create a Summable
protocol that says any type that conforms must have the addition operator +
available. Then, you specify that the Int
and Double
types conform to it.
Now using a generic type T
and a type constraint, you can create a generic function adder
:
func adder<T: Summable>(x: T, _ y: T) -> T { return x + y } |
You’ve reduced your two functions (actually more, since you would have needed more for other Summable
types) down to one and removed the redundant code. You can use the new function on both integers and doubles:
let adderIntSum = adder(1, 2) let adderDoubleSum = adder(1.0, 2.0) |
And you can also use it on other types, such as strings:
extension String: Summable {} let adderString = adder("Generics", " are Awesome!!! :]") |
By adding other conforming types to Summable
, your adder(_:_:)
function becomes more widely available thanks to its generics-powered definition!
Extending a Generic Type
Extend the Queue
type and add the following method right below the Queue
definition:
extension Queue { func peek() -> Element? { return elements.first } } |
peek
returns the first element without dequeuing it. Extending a generic type is easy! The generic type parameter is visible just like in the original definition’s body. You can use your extension to peek into a queue:
q.enqueue(5) q.enqueue(3) q.peek() |
You’ll see the value 5
as the first element in the queue, but nothing has been dequeued and the queue has the same number of elements as before.
Challenge: Extend the Queue
type to implement a function homogeneous
that checks if all elements of the queue are equal. You’ll need to add a type constraint in the Queue
declaration to ensure its elements can be checked for equality to each other.
New Generics Features in Swift 2
Along with many other language features, generics got a nice update in the latest Swift 2 release. Here, you’ll review some of the latest updates.
Subclassing Generic Classes
Swift 2 added the ability to subclass generic classes, which can be useful in some cases. Previously, you could subclass generic classes but they had to also be generic. Now, you can make a more “concrete” subclass of a generic class.
Add the following generic class to the playground:
class Box<T> { } |
You define a Box
class. The box can contain anything, and that’s why it’s a generic class. There are two ways you could subclass Box
:
- You might want to extend what the box does and how it works but keep it generic, so you can still put anything in the box;
- You might want to have a specialized subclass that always knows what’s in it.
Swift 2 allows both. Add this to your playground:
class Gift<T>: Box<T> { } class StringBox: Box<String> { } |
You define two Box
subclasses here: Gift
is a special kind of box that will presumably have different methods and properties defined on it, such as recipient
and wrap()
. However, it still has a generic on the type, meaning it could contain anything.
In contrast, StringBox
is a box of strings, and thus isn’t generic anymore.
Declare instances of each class under the subclass declarations:
let box = Box<Int>() let gift = Gift<Double>() let stringBox = StringBox() |
Notice that the StringBox
initializer doesn’t need to take the generic type parameter anymore, since it’s fixed in the declaration of StringBox
.
Enums With Multiple Generic Associated Values
Before WWDC 2015, when Swift still didn’t support the new error-handling mechanism, the most popular way to do “functional” error-handling was using a so-called result enum
. This is a generic enum
with two associated values: one for the actual result value and one for the possible error. Swift didn’t support this, and you would get a compiler error if you were lucky, and strange undefined behavior if you weren’t.
There were hacks to work around this limitation. Thankfully, Swift 2 supports declaring such a result type, among other useful enums
with multiple generic associated values.
Add the following declaration to the end of your playground:
enum Result<ValueType, ErrorType> { case Success(ValueType) case Failure(ErrorType) } |
The main use case for such an enum
is as a return value for a function with specific error information, kind of like a more general optional:
func divideOrError(x: Int, y: Int) -> Result<Int, String> { guard y != 0 else { return Result.Failure("Division by zero is undefined") } return Result.Success(x / y) } |
Here, you declare a function that divides two integers. If the division is legal, you return the resulting value in the .Success
case; otherwise you return an error message.
Try out your new function:
let result1 = divideOrError(42, y: 2) let result2 = divideOrError(42, y: 0) |
The first result is a success case with a value of 21, and the second result is the failure case with the message Division by zero is undefined
. Even though the enum type has two types associated with it, you can see how the cases are defined to use just one or the other.
Where to Go From Here?
Here’s the downloadable final playground with all the code you’ve written in this tutorial.
Generics are at the core of many common language features, such as arrays and optionals. You’ve seen how to use them to build your own container types and to avoid duplicating code that would only differ by type.
For more information, read through the “Generics” chapter and the “Generic Parameters and Arguments” language reference chapter of Apple’s guide, The Swift Programming Language. You’ll find more detailed information about generics in Swift, as well as some handy examples.
Thank you for taking the time to learn about generics in Swift, an integral feature that you’ll use everyday to write powerful and type-safe abstractions. You can get the best of both worlds – generic classes, while specifying an actual type to get type safety when you instantiate it.
If you have any questions, please let me know in the forum discussion below! :]
The post Swift Tutorial: Introduction to Generics appeared first on Ray Wenderlich.