Functions β
In Basic Operators and Functions, you learned how to use functions, like sqrt
and isMultiple(of:)
. In this chapter, youβll learn how you can declare and use your own functions.
A function is a reusable piece of code. It can have parameters that configure its behavior, it performs a task, and it can return a result.
For example:
sin(0.5 * .pi)
This code calls the sin
function, which has a single parameter, an angle. In this example, the function calculates and returns the sine of the angle 0.5 * .pi
.
Declaring and calling a function β
The following example declares a function named printGreeting
:
func printGreeting() {
print("Hello")
}
This function does not have any parameters and does not return a value. It simply prints a greeting.
A function declaration consists of the keyword func
, a name for the function, a parameter list, and a body. Parentheses are required around the parameter list, even when itβs empty.
Note
Function names use camel case, just like constants and variables.
To call a function, you use its name:
printGreeting()
A function call always includes parentheses, even when the function has no parameters.
When you call a function, the control process jumps from the function call into the body of the function. After it finishes executing this body, it returns back to where it was and continues executing the code.
Parameters and arguments β
Parameters make a function reusable. They provide input the function can use to configure its behavior or calculate its result. The following function declares a parameter that configures a greeting:
func sayHello(person: String) {
print("Hello, \(person)")
}
Parameters are local constants scoped to the body of the function. You declare a parameter just like a constant. However, you donβt use the keyword let
because parameters can only be constants, not variables.
To declare multiple parameters, you separate them with commas:
func greet(person: String, greeting: String) {
print("\(greeting), \(person)")
}
When you call a function that has parameters, you provide a value, known as an argument, for each of the parameters. For example:
sayHello(person: "Steven")
This calls the function sayHello
and assigns the argument "Steven"
to person
. As you can see, you include the name of the parameter as an argument label in the function call.
Argument labels add clarity to a function call and make it clear which argument is being assigned to which parameter. However, this doesnβt mean you can reorder them. The order of the arguments must match the order of the parameters in the function declaration.
The following example is invalid:
greet(greeting: "Hello", person: "Steven")
Return values β
A function can return a value. You specify the type of this value in the function declaration:
func max(a: Int, b: Int) -> Int {
let result = a > b ? a : b
return result
}
This function has a return type of Int
. Inside the body, you use a return
statement to specify the return value and end the function.
When you call a function that has a return value, Swift replaces the function call with its return value. The following example calls max
and assigns its return value to result
:
let result = max(a: 4, b: 2)
You can also use the return
statement in functions that donβt return a value. In this case, it simply ends the function. For example:
var currentUser = "Alice"
func switchUser(user: String) {
if user == currentUser {
return
}
currentUser = user
print("Welcome \(user)")
}
This function does nothing when you call it with a user
thatβs already the currentUser
.
You can achieve the same result by inverting the condition of the if
statement:
func switchUser(user: String) {
if user != currentUser {
currentUser = user
print("Welcome \(user)")
}
}
However, this solution requires additional indentation. In general, you want to keep the main path through your code unindented to improve its readability. An early return
statement helps achieve this.
Implicit return statements β
A function can often calculate its return value with a single line of code. For example, you can shorten the declaration of max
to:
func max(a: Int, b: Int) -> Int {
return a > b ? a : b
}
When a function consists entirely of a return
statement, you can remove the return
keyword and specify only the return value:
func max(a: Int, b: Int) -> Int {
a > b ? a : b
}
This function has an implicit return statement.
Argument labels β
Your goal when naming a function and its parameters is to achieve clarity at the point of use. In other words, a function call should read like a sentence. Argument labels play a significant role in this.
You can configure an argument label by declaring two names for a parameter. The first name becomes the argument label; the second is used in the function body. For example:
func sayHello(to person: String) {
print("Hello, \(person)")
}
This function has a parameter with an internal name of person
and an external name of to
. You call this function as follows:
sayHello(to: "my little friend")
You can make a similar improvement to the switchUser
function:
func switchUser(to user: String) {
if user == currentUser {
return
}
currentUser = user
print("Welcome \(user)")
}
switchUser(to: "Bob")
When argument labels add clutter, not clarity, you can remove them by using the wildcard pattern (_
) as an external name. In the following example, the first parameter is the direct object of the sentence, and doesnβt require an argument label:
func greet(_ person: String, with greeting: String) {
print("\(greeting), \(person)")
}
greet("Steven", with: "Hello")
In the case of the function max
, the parameter names, whether they be a
and b
, or x
and y
, are of no importance to the caller of the function, so you should remove their argument labels:
func max(_ a: Int, _ b: Int) -> Int {
a > b ? a : b
}
With this declaration, a call to max
becomes max(4, 2)
, which is just as clear as max(a: 4, b: 2)
, but less verbose and easier to read.
Note
To help with clear and consistent naming, Swift defines a set of API Design Guidelines. This course follows these guidelines and attempts to teach by example.
When referring to a function by name, you can include the argument labels, even wildcards, to avoid ambiguity. For example, here are the functions youβve declared so far:
printGreeting
sayHello(person:)
greet(person:greeting:)
max(a:b:)
switchUser(user:)
sayHello(to:)
switchUser(to:)
greet(_:with:)
max(_:_:)
Overloading β
Did you notice the previous list contains multiple functions with the same name?
sayHello(person:)
andsayHello(to:)
greet(person:greeting:)
andgreet(_:with:)
max(a:b:)
andmax(_:_:)
switchUser(user:)
andswitchUser(to:)
Swift lets you declare multiple functions with the same name; this is known as overloading. This technique is helpful in declaring related functions, but you should use it sparingly to avoid confusion.
You may overload a function only when the compiler can unambiguously determine which function is being called. Therefore, overloaded functions must differ on at least one of the following points:
- The number of parameters.
- The external parameter names.
- The types of the parameters.
- The return type.
The functions listed above all differ in their external parameter names.
Here are a few more examples:
func max(_ a: Int, _ b: Int, _ c: Int) -> Int {
if a > b {
return a > c ? a : c
} else {
return b > c ? b : c
}
}
func max(_ a: Double, _ b: Double, _ c: Double) -> Double {
if a > b {
return a > c ? a : c
} else {
return b > c ? b : c
}
}
Neither of these conflict with the already declared function max(_:_:)
, or with each other:
- The first example has three parameters, whereas
max(_:_:)
only has two. - The second example has the same number of parameters and the same external parameter names as the first example, but it has different parameter types and a different return type.
Overloads that differ only in their return type are less common. The following functions are an example of this. Both round a number to the nearest integer. The first returns its result as an Int
; the second as a Double
:
func rounded(_ x: Double) -> Int {
let remainder = x.truncatingRemainder(dividingBy: 1)
switch remainder {
case _ where remainder <= -0.5:
return Int(x - 1)
case _ where remainder >= 0.5:
return Int(x + 1)
default:
return Int(x)
}
}
func rounded(_ x: Double) -> Double {
let remainder = x.truncatingRemainder(dividingBy: 1)
switch remainder {
case _ where remainder <= -0.5:
return Double(Int(x - 1))
case _ where remainder >= 0.5:
return Double(Int(x + 1))
default:
return Double(Int(x))
}
}
Because these functions differ only in their return type, you need a type annotation to disambiguate between them:
let roundedNumber: Double = rounded(-0.5)
Here, the compiler picks the version of rounded(_:)
that returns a Double
. Without the annotation, this code would be invalid.
Default parameters β
A function can assign default values to its parameters:
func greet(_ person: String, with greeting: String = "Hello") {
print("\(greeting), \(person)")
}
A call to this function can either provide a value for greeting
or use the default value "Hello"
:
greet("Steven", with: "Hi")
greet("Steven")
The print
function also uses default parameters. In Control Flow and Booleans, you used the following loop to print a number of dots:
var dots = 3
while dots > 0 {
print(".", terminator: "")
dots -= 1
}
print()
The print
function has a terminator
parameter with a default value of \n
. By setting this parameter to an empty string, you stop the function from adding a newline character so that you can print multiple strings on the same line. The final call to print
prints an empty string with a newline terminator, so subsequent text appears on a new line.
Although Swift doesnβt require it, default parameters should be at the end of the parameter list. That way, they donβt affect the position of the required parameters.
Recursion β
A function may call itself as part of its implementation. This technique is known as recursion.
Hereβs a function that uses recursion to calculate the sum of the numbers 1 to n
:
func sumOfOneTo(_ n: Int) -> Int {
if n == 1 {
1
} else {
sumOfOneTo(n - 1) + n
}
}
Note
This function has an implicit return statement that returns the result of an if
expression.
Hereβs how this recursion works:
- When
n
is 1, the function returns 1. This is the base case. - For any
n
larger than 1, the function addsn
to the sum of the numbers 1 ton - 1
. This is the recursive case.
A recursive function must have a base case. Without it, the recursion wonβt end until the program runs out of memory and crashes.
Recursion can be an elegant solution to many problems. However, it comes at a cost. To illustrate, hereβs what happens when you call sumOfOneTo(3)
:
sumOfOneTo
is called with an argument of 3.sumOfOneTo(3)
makes a recursive call tosumOfOneTo(2)
.sumOfOneTo(2)
makes a recursive call tosumOfOneTo(1)
.sumOfOneTo(1)
is the base case and returns 1.sumOfOneTo(2)
adds 2 to this value and returns 3.sumOfOneTo(3)
adds 3 to this value and returns 6.
After step 3, three function calls are waiting to complete. These calls are using memory that cannot be freed until the base case is reached, and the recursion starts to unwind.
A non-recursive implementation often performs better than a recursive one. For comparison, hereβs an implementation of sumOfOneTo
that doesnβt use recursion:
func sumOfOneTo(_ n: Int) -> Int {
var sum = 0
for number in 1...n {
sum += number
}
return sum
}
This implementation doesnβt require additional function calls and performs much better.
Nevertheless, recursion is an important technique to master.
Up next β
Up next is another set of exercises. Work your way through these exercises, then continue on to your first challenge, where youβll build a game of Tic-Tac-Toe.