Chapter 4. Functions and Enums: Reusing Code on Demand
Functions in Swift allow you to package up a specific behavior or unit of work into a single block of code that you can call from other parts of your program. Functions can be standalone, or they can be defined as part of a class, a structure, or an enumeration, where they are usually referred to as methods. Functions let you break down complex tasks into smaller, more manageable, and more testable units. They’re a core part of the way you structure a program with Swift.
Functions in Swift let you reuse code
Like many programming languages, Swift has a feature known as functions. Functions let you package up some code, and reuse it if you need to run it more than once.
We’ve been using functions a fair bit already: the print
function, for example, is a built-in function that lets us print things for display. Swift has lots of other built-in functions, and you’ll get a chance to use some of them in a moment.
What can functions do?
Functions can, in no particular order, do all sorts of things, including:
Note
Including, but not limited to!
Be defined as a block of code that just does something, and doesn’t need any inputs or have any outputs.
Take as many inputs of specific types as they need.
Return as many outputs of specific types as they need.
Have a variable number of inputs.
Modify the original values of variables passed to them, separately to also returning values (or not).
Functions let you package up code that performs specific tasks, give it a name, and reuse it by calling it at will.
Built-in functions
Before we look at implementing some functions of our own, let’s take a few moments to consider some of Swift’s built-in functions. These are built directly into the language.
The first two we’ll consider are min
and max
. You might be able to guess from their names that they will return their smallest and largest arguments (respectively) as their result.
Here’s a really simple example of both that you can test in a Playground:
As you would expect, the first example will print out "-3"
, since that is the smallest value in the argument list.
The second example might look odd at first, but it will print "zoo"
. That’s because, since the arguments are all strings, the comparison between the elements in the list is a string comparison, and “zoo” comes after “barn” and “cinema,” so it’s considered to be greater than them.
What can we learn from built-in functions?
From just these two functions, we can glean some important information about functions in Swift:
The functions are taking some input values (called arguments to the function) and returning a result.
Some functions can support a variable number of arguments.
min
andmax
, for example, can take an arbitrary number of arguments as long as there are two or more, and they’re all of the same type.Note
We call this feature a variadic parameter—we’ll cover it soon.
Some functions support more than one type of argument. For example, both
min
andmax
can work with integers, floating-point values, and strings, as long as all arguments are of the same type. Under the hood, if Swift knows how to compare two instances of a type, then that type can be used withmin
andmax
.Note
Understanding how this “generic” argument feature works will require you to have some more Swift under your belt first. We’ll get you there, promise!
You may have remembered from the previous chapters, or at least deduced from the preceding examples, that print
is also a built-in function.
It can take a variable number of arguments, and those arguments can be of any type. Swift already knows how to print simple types like numbers and strings, and complex types like arrays and dictionaries.
For user-defined types (like classes and structures), by default print
will output the type name, but you can add a special property to a type that describes how it should be represented in string form. If such a description exists, print
will output it instead of the type name.
Improving the situation with a function
On the previous pages, we had some messy Swift code that basically did the same thing over and over again: it checked if enough pizzas were ordered to qualify for a special volume discount, and then it printed out the count and type of pizza that was ordered.
Let’s write a pizzaOrdered
function.
A function is defined with the func
keyword, followed by the desired name of the function, any parameters the function has, and then the function’s return type.
Our pizzaOrdered
function will be able to take care of the ordering process from the previous page, but with less potentially error-prone code. It has a name, which we already know, plus two parameters: the name of the pizza as a string, and the count of the pizza as an integer. It doesn’t have a return type (because it doesn’t return anything, it just prints things).
Here’s the definition for our new function:
Writing the body of the function
The body of the function is where the magic (or, in this case, the reusable piece of logic) happens. In the case of our pizzaOrdered
function we need to do two things—the same two things the fine code earlier was doing:
Functions allow you to take redundant code, or code that needs to be used over and over again with minimal differences, and store it in a place where you can reuse it.
Using functions
Going back to our messy, convoluted pizza ordering code from earlier, because of the function we’ve written we can replace the entire page of code we had earlier with this instead:
We could also just pass the values in directly as arguments, without having to create or use intermediate variables to represent them. For example, this code is valid too and does exactly the same thing:
pizzaOrdered(pizza: "Hawaiian", count: 7)
Argument labels and parameter names are actually two different things. The argument labels and parameter names happen to be the same when we define our function like this:
But we can also independently specify argument labels and parameter names:
Our function would then be called as follows, noting that the body of the function would still be working with the same names (the parameter names) internally:
pizzaOrdered(thePizza: "Hawaiian", theCount: 7)
We can make functions cleaner by thinking about their names and the argument labels for their parameters.
In the case of our pizzaOrdered
function, because pizza is in the name of the function, we might want to omit the requirement to label that first parameter. Swift likes to encourage you to write functions and code that are mostly readable as plain English. Read this call to our pizzaOrdered
function out loud:
pizzaOrdered(pizza: "Vegetarian", count: 5)
Notice how you say “pizza ordered pizza.” That second “pizza” is superfluous to understanding what the function does. We can remove it by updating the function definition to look like this:
func pizzaOrdered(_ pizza: String, count: Int) { if(pizzaCount > 5) { print("Because more than 5 pizzas were ordered, a discount of 10% applies to the order.") } print(″\(count)x \(pizza) pizzas were ordered.") }
This means you can call the function like this, instead:
pizzaOrdered("Vegetarian", count: 5)
If you read that out loud, you’ll understand why it’s preferable like this for clarity.
The use of the underscore _ instead of an argument label allows for it to be omitted. You can include the underscore in as many of the parameters as you want.
Functions deal in values
Whether you’re familiar with the way other programming languages work or are just trying to get to grips with what happens when a function is called in Swift, it’s important to acknowledge that nothing you pass in to a function will modify the original value unless you ask for it to be modified. You can test this yourself:
Create a function that performs some simple math
Let’s make a function that multiplies a number by 42:
func multiplyBy42(_ number: Int) { number = number * 42 print("The number multiplied by 42 is: \(number)") }
Test out your function
Calling your function with a value should print the result of that value being multiplied by 42:
multiplyBy42(2)
This should print out
"The number multiplied by 42 is: 84".
Create a variable
Create a variable to test the function with:
var myNumber = 3
Call the function, passing the variable as the value
Call the
multiplyBy42
function, passing the variablemyNumber
as the value:multiplyBy42(myNumber)
The value that’s stored in
myNumber
(in this case,3
) is copied into the parameter of the function.myNumber
is never modified.This should print out
"The number multiplied by 42 is: 126"
.Check that your variable is unmodified
Add a call to
print
, to check that the originalmyNumber
is unmodified:print("The value of myNumber is: \(myNumber)")
You can create a special kind of parameter that allows you to modify the original value.
If you need to write a function that does modify the original value, you can create an inout
parameter.
If you wanted to write a function that takes a string variable as its sole parameter, and turns that string variable into the string "Bob"
, you would do it like this:
func makeBob(_ name: inout String) { name = "Bob" }
You could then use any string variable, like this one:
var name = "Tim"
And, by passing it in to the makeBob
function, make it into a Bob:
Now, the value inside name
will be "Bob"
. You can check this by printing name
:
print(name)
The use of an inout parameter means your parameter can never have a constant or a literal value passed to it.
Many happy returns (from your functions)
The functions you’ve made so far haven’t returned anything. That is to say, they’ve done stuff, but they haven’t sent anything back to where they were called from. Functions can do that too. You can define a function with a return type. That tells Swift what kind of value the function will return. This value can then be used, or stored, as needed.
Take a look at the following function:
But Swift is meant to make things simple...
So you can combine the creation of the message and the return into a single line, like this:
func welcome(name: String) -> String { return "Welcome to the Swift Pizza shop, \(name)!" }
Or you can make things even simpler with very simple functions, and take advantage of Swift’s implicit return feature. If the entire body of a function is a single expression, then it’s just returned for you:
func welcome(name: String) -> String { "Welcome to the Swift Pizza shop, \(name)!" }
You can provide a default value for any parameter in a function.
When you define your function, you can provide a default value for any of the parameters. If we wanted to add a default value to the welcome
function we created on the previous page, it would look like this:
When a function has a default value, you can omit the parameter when you call the function if you like.
A variable number of parameters
Note
(Variadic parameters)
There are times when it is useful to write a function that can take a variable number of parameters. For example, you could write a function that takes a list of numbers, and calculates their average. That function would be far more flexible if you could list as many or as few numbers as you wanted every time you called it.
Note
Sometimes you need three cookies, sometimes you only need one.
This feature is available in Swift—it’s called a variadic parameter. Here’s how we could use it to implement a function to calculate the average of a list of numbers:
Inside the function, the variadic parameter’s arguments are available as an array called numbers
(from the parameter name). We use a for
loop to iterate through that array, calculating the total as we go, and at the end, we divide the total by the count of numbers in the array to return the average.
Note
You’ve already seen some looping and array code. We’ll get to them in more detail in some upcoming chapters.
We could call our function like this:
Fireside Chats
Tonight’s talk: What’s in a name?
What can you pass to a function?
The short answer is: you can pass literally anything to a function. The slightly longer answer is: you can pass anything you might need to a function. Any Swift value can be passed as a parameter, including a string, a Boolean, an integer, an array of strings...anything:
But it’s not just values that are fair game as parameters for your functions. Expressions work as well:
You can also pass variables and constants in to your functions as parameters, which is actually what you’ll be doing most of the time. Here are some calls to the same function, doAThingWithA
, using variables and constants instead of just values:
Every function has a type
When you define a function, you’re also defining a function type. A function type is made up of the parameter types of the function as well as the return type of the function.
These are all examples of function types:
Note
This era started with the debut of the C programming language.
In the early days of programming, people went a bit over the top using functions for everything they could possibly think of. This led to very modular but very hard-to-read programs.
Try to strike a balance. If you’re coming to Swift from another programming language, you probably already have your own personal baseline for how modular you go with functions. If that’s working for you, bring it to Swift!
You don’t need to modularize anything, and Swift’s functions are not particularly expensive. You’ll know the right balance when you see it. Keep functions to doing only one thing.
Function types work like every other type.
Because a function type is just a type, as far as Swift is concerned, you can use them just like any other type.
It’s particularly useful to be able to define a variable of a certain function type:
var manipulateInteger: (Int, Int) -> Int
And then create a function that matches that function type:
func addNumbers(_ first: Int, _ second: Int) -> Int { return first + second }
Assign the function to the variable:
manipulateInteger = addNumbers
and you can then use the assigned function via the variable you assigned it to:
Because the manipulateInteger
variable can store any function with the same type, you can reassign it:
Because this is just a type like any other, you can let Swift infer the function type when you create the variable, too:
Function types as parameter types
As you might have picked up on, function types are just types as far as Swift is concerned. This means that you can use a function type as the parameter type when you’re creating a function.
Building on what you did previously, consider the following function:
Again: function types work like every other type.
Function types are types in every way:
typealias Pizza = String func makeHawaiianPizza() -> Pizza { print("One Hawaiian Pizza, coming up!") print("Hawaiian pizza is mostly cheese, ham, and pineapple.") return "Hawaiian Pizza" } func makeCheesePizza() -> Pizza { print("One Cheesey Pizza, coming up!") print("Cheesey pizza is just cheese, more cheese, and more cheese.") return "Cheese Pizza" } func makePlainPizza() -> Pizza { print("One Plain Pizza, coming up!") print("This pizza has no toppings! Not sure why you’d order it.") return "Plain Pizza" } func order(pizza: String) -> () -> Pizza { if (pizza == "Hawaiian") { return makeHawaiianPizza } else if (pizza == "Cheese") { return makeCheesePizza } else { return makePlainPizza } } var myPizza = order(pizza: "Hawaiian") print(myPizza())
Multiple return types
Great idea. Anything for a busy function.
A function can return a tuple containing as many values as needed. For example, if you needed a greeting function for your pizza shop that took a name and returned hello and goodbye messages/greetings as strings, you could do this:
You’d then be able to call the function, and access the hello greeting as follows:
Fireside Chats
Tonight’s talk: The Order of Things
The Swift Teacher: | Student: |
---|---|
I believe you have a question, Student... | |
Yes, Teacher. If I write a function where every parameter has an argument name, why can’t I call it with the arguments in any order I want? | |
How quickly you forget, Student! | |
Forget? Have you taught me about this already, Teacher? | |
Yes, Student. We have talked about this in the past. I thought at the time that you fully grokked the lesson. | |
That I fully grokked the lesson? Teacher! What have I told you about not trying to fit in with the apprentices? | |
I don’t recall, Student—I’m a better teacher than I am a student. But back to your question: when you call a function, the compiler first determines what its signature must be, and then calls the function with that signature. | |
And if I switch the argument order, I’m using a signature for a function that isn’t defined! I get it now! | |
Yes Student, but do you grok it? | |
TEACHER!! |
Functions don’t have to stand alone
All of the examples of functions that we’ve considered in this chapter have been what are called standalone or global functions. They’re defined at global scope and can be called from anywhere else in your program.
You may recall, though, that we hinted at the start of this chapter that functions can also be defined as part of a class. When we define functions in this way, they’re sometimes referred to as methods. And using functions (or methods) as part of a class is one of the hallmarks of object-oriented programming. Swift goes where some other languages don’t by also allowing you to define functions as part of structures and enumerations.
Everything we’ve covered in this chapter in relation to functions also applies when they are defined in those other contexts. You don’t have to unlearn anything—the syntax, use of argument and parameter names, variadics, and the inout annotation all apply equally with functions that are part of a class, structure, or enumeration.
Nested functions
Functions can also be defined inside other functions. This can be a convenient way to simplify an otherwise overly complex function that requires a lot of repeated code or expression evaluation, without the need to declare another function at global scope.
Enums allow you to create groups of related values.
When you’re playing a complex board game, you might need to represent multiple states a player could be in. For example:
var playerState: String playerState = "dead" playerState = "blockaded" playerState = "winner"
This is sub-par, and hard to manage, and potentially unsafe or fragile if you mistype one of the strings you’re using to represent state. Basically, it’s terrible. Meet enums!
enum PlayerState { case dead case blockaded case winner }
Now, when we talk about a player state, we can use the enum instead of the potentially fragile string:
func setPlayerState(state: PlayerState) { print("The player state is now \(state)") } setPlayerState(state: .dead)
Functioncross
Let’s give your left brain a break, and try out a quick crossword. All of the solutions are related to concepts covered in this chapter!
Across
1) A type of parameter that can support multiple values.
5) Inside a function, a parameter is _______.
6) The external name for a function parameter.
7) A function defined inside another function is said to be _______.
8) How Swift uniquely identifies a function.
10) The maximum number of variables a function can have.
11) Character to supress an argument name.
13) Statement to define a block of code to be executed later.
Down
2) Assign a value to a parameter in a function definition to make it a _______ value.
3) Parameter that can be modified inside a function.
4) The sort of entity that has to be passed as an inout parameter.
5) A keyword that can’t be deferred
9) Return type for a function that doesn’t return anything.
12) Ensure code is executed before function ends.
Answers in “Functioncross Solution”.
Functioncross Solution
From “Functioncross”.
Get Head First Swift now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.