Posted 9/22/2018.
You can find Part 3 here.
Functions are pieces of code that perform a specific task. A function can also be thought of as a set of instructions packaged together. In Part 1, you used the built-in Swift function print
to output values to the console. So far you have used it to output "Hello, World!", "Hello, Xcode!", pi, and an email address.
When you use a function you "call" it. Much like with an electronic printer where you type Command-P to print any document, when you call the print
function you call it the same way whether you are printing "Hello, World!" or an email address.
This brings us to one of the most important principles of software development, Don't Repeat Yourself! or DRY. Functions are reusable sets of instructions. If you ever hear a programmer talk about "DRY" code, this is what they mean.
Functions can have parameters which customize their behavior. When you print a document with an electronic printer, you can specify whether you want the printer to print in color or grayscale, single or double-sided, and all or a range of pages. When you use a microwave, you can specify the cooking time and power level.
So when we say functions "perform a specific task" we don't mean a task so specific that it will only be used once. You wouldn't want a microwave that was only capable of reheating the exact leftovers from last night's dinner. You also wouldn't want a device that both heats food and dries clothes. Determining the specific task each function should perform is where programming becomes more of an art than a science.
Functions are defined with the func
keyword. Open the "HelloSwift" project from the last two parts. To define a simple function that prints "Hello, World!" to the console, type the following at the bottom of main.swift
:
func printHelloWorld() {
print("Hello, World!")
}
As you can see, functions can call other functions. In fact, all our new function does is call the print
function we have already been using. This is a bad function! It clearly violates the DRY principle discussed above. We don't want to have to write printHelloXcode
and printEmailAddress
functions when we could just use the built-in print
function with any value.
Let's define a more useful function that will say "Hello" to any name we specify:
func sayHelloToPerson(name: String) {
print("Hello, " + name + "!")
}
To call this function we would write:
sayHelloToPerson(name: "Todd")
The place in code where a function is called is referred to as the "call site". Try calling the function a few times with your own name and the names of family and friends.
In this function, "sayHelloToPerson" is the name of the function and "name" is a parameter of type String
. Apple intended for Swift functions to read like grammatical English sentences when they are called.* Our sayHelloToPerson
function doesn't currently read like English. To solve this, Swift has the concept of argument labels. We can rewrite our function as follows:
func sayHelloToPerson(withName name: String) {
print("Hello, " + name + "!")
}
In this example, "withName" is the argument label. Now to call it, we would write:
sayHelloToPerson(withName: "Todd")
This version reads more naturally. However, in the body of the function, it wouldn't make sense to use the argument label:
print("Hello, " + withName + "!")
This is why we have both parameters and argument labels in Swift. It allows us to have the best of both worlds, where the call site and function body are both easy to read and understand.
Functions can also return a value when they are called. Let's look at an example, which returns the full name of a person from their first and last names:
func fullNameForPerson(withFirstName firstName: String, lastName: String) -> String {
let fullName = firstName + " " + lastName
return fullName
}
We use the notation -> Type
in the function definition to denote what type of value the function will return. To return a value from a function, we use the keyword return
before the value in the body of the function. We can also return an expression directly. Our function can be rewritten like this:
func fullNameForPerson(withFirstName firstName: String, lastName: String) -> String {
return firstName + " " + lastName
}
To call it, we would write:
let fullName = fullNameForPerson(withFirstName: "Todd", lastName: "Kramer")
print(fullName)
Try calling this function with your own name and a few others. OK, not very exciting.
Functions can return any type. Let's write one that determines whether there is enough pizza based on the number of people, slices per pie, and slices per person.
func isEnoughPizza(forNumberOfPeople numberOfPeople: Int, slicesPerPizza: Int, slicesPerPerson: Int) -> Bool {
return (numberOfPeople * slicesPerPerson) <= slicesPerPizza
}
Now let's call this function twice with different values and print the results:
let isEnoughPizzaForParty1 = isEnoughPizza(forNumberOfPeople: 5, slicesPerPizza: 8, slicesPerPerson: 2)
let isEnoughPizzaForParty2 = isEnoughPizza(forNumberOfPeople: 3, slicesPerPizza: 12, slicesPerPerson: 3)
print(isEnoughPizzaForParty1)
print(isEnoughPizzaForParty2)
The console should tell you that there isn't enough pizza for party #1 (false
) but there is enough for party #2 (true
).
At the beginning of the tutorial we talked about the DRY principle. Our isEnoughPizza
function is useful, and if our app were focused only on pizza it would be a perfectly reasonable function to write. But what if we need to determine whether there is enough cake? Or water? Or seats in a theater? Let's rewrite our function to be useful in all of those cases.
func isEnough(forNumberOfPeople numberOfPeople: Int, unitsPerWhole: Int, unitsPerPerson: Int) -> Bool {
return (numberOfPeople * unitsPerPerson) <= unitsPerWhole
}
For water we might use the function as follows:
let ouncesPerBottle = 64
let ouncesPerPerson = 8
let isEnoughWater = isEnough(forNumberOfPeople: 7, unitsPerWhole: ouncesPerBottle, unitsPerPerson: ouncesPerPerson)
print(isEnoughWater)
OK, now our function is more useful, but it's a bit harder to read at the call site. One nice thing about the isEnoughPizza
function is that its argument labels are descriptive. When you call the function, it asks you for slicesPerPizza
and slicesPerPerson
instead of unitsPerWhole
and unitsPerPerson
. Our goal in writing a function that is useful for more cases than just pizza was to reuse the logic, but sometimes that can make our code more difficult to read.
To reuse logic AND make our code easy to read, we can write another function that simply calls this new one.
func isEnoughWater(forNumberOfPeople numberOfPeople: Int, ouncesPerBottle: Int, ouncesPerPerson: Int) -> Bool {
return isEnough(forNumberOfPeople: numberOfPeople, unitsPerWhole: ouncesPerBottle, unitsPerPerson: ouncesPerPerson)
}
Now the call site is much easier to read:
let isEnoughWaterFor5KRace = isEnoughWater(forNumberOfPeople: 7, ouncesPerBottle: 64, ouncesPerPerson: 8)
print(isEnoughWaterFor5KRace)
You should now be able to answer these questions:
In this tutorial we saw how to write functions in Swift and the importance of writing reusable code. Next we'll look at Conditional Logic!
* You can find this in Apple's official Swift "API Design Guidelines" here. Under "Strive for Fluent Usage", the first guideline is "Prefer method and function names that make use sites form grammatical English phrases." This document is a great starting point for Swift language best practices.