Day 05
Day 05 êŽë š
Functions
Functions let us wrap up pieces of code so they can be used in lots of places. We can send data into functions to customize how they work, and get back data that tells us the result that was calculated.
Believe it or not, function calls used to be really slow. Steve Johnson, the author of many early coding tools for the Unix operating system, said this:
âDennis Ritchie (the creator of the C programming language) encouraged modularity by telling all and sundry that function calls were really, really cheap in C. Everybody started writing small functions and modularizing. Years later we found out that function calls were still expensive, and our code was often spending 50% of its time just calling them. Dennis had lied to us! But it was too late; we were all hooked...â
Today you have 11 one-minute videos to watch, and youâll meet things like variadic functions, throwing errors, and more. Once youâve watched each video and optionally gone through the extra reading, thereâs a short test to help make sure youâve understood what was taught.
1. Writing functions
1. Writing functions
Functions let us re-use code, which means we can write a function to do something interesting then run that function from lots of places. Repeating code is generally a bad idea, and functions help us avoid doing that.
To start with, weâre going to write a function that prints help information for users of our app. We might need this anywhere in our app, so having it as a function is a good idea.
Swift functions start with the func
keyword, then your function name, then open and close parentheses. All the body of your function â the code that should be run when the function is requested â is placed inside braces.
Letâs write the printHelp()
function now:
func printHelp() {
let message = """
Welcome to MyApp!
Run this app inside a directory of images and
MyApp will resize them all into thumbnails
"""
print(message)
}
We can now run that using printHelp()
by itself:
printHelp()
Running a function is often referred to as calling a function.
1. Writing functions - Additional
- Optional: What code should be put in a function?
- Test: Writing functions
2. Accepting parameters
2. Accepting parameters
Functions become more powerful when they can be customized each time you run them. Swift lets you send values to a function that can then be used inside the function to change the way it behaves. Weâve used this already â weâve been sending strings and integers to the print()
function, like this:
print("Hello, world!")
Values sent into functions this way are called parameters.
To make your own functions accept parameters, give each parameter a name, then a colon, then tell Swift the type of data it must be. All this goes inside the parentheses after your function name.
For example, we can write a function to print the square of any number:
func square(number: Int) {
print(number * number)
}
That tells Swift we expect to receive an Int
, and it should be called number
. This name is used both inside the function when you want to refer to the parameter, but also when you run the function, like this:
square(number: 8)
2. Accepting parameters - Additional
3. Returning values
3. Returning values
As well as receiving data, functions can also send back data. To do this, write a dash then a right angle bracket after your functionâs parameter list, then tell Swift what kind of data will be returned.
Inside your function, you use the return
keyword to send a value back if you have one. Your function then immediately exits, sending back that value â no other code from that function will be run.
We could rewrite our square()
function to return a value rather than print it directly:
func square(number: Int) -> Int {
return number * number
}
Now we can grab that return value when the function is run, and print it there:
let result = square(number: 8)
print(result)
If you need to return multiple values, this is a perfect example of when to use tuples.
3. Returning values - Additional
4. Parameter labels
4. Parameter labels
We wrote our square()
function like this:
func square(number: Int) -> Int {
return number * number
}
That names its parameter number
, so we can use number
inside the function to refer to it, but we must also use the name when running the function, like this:
let result = square(number: 8)
Swift lets us provide two names for each parameter: one to be used externally when calling the function, and one to be used internally inside the function. This is as simple as writing two names, separated by a space.
To demonstrate this, hereâs a function that uses two names for its string parameter:
func sayHello(to name: String) {
print("Hello, \(name)!")
}
The parameter is called to name
, which means externally itâs called to
, but internally itâs called name
. This gives variables a sensible name inside the function, but means calling the function reads naturally:
sayHello(to: "Taylor")
4. Parameter labels - Additional
- Optional: Why does Swift use parameter labels?
- Test: Parameter labels
5. Omitting parameter labels
5. Omitting parameter labels
You might have noticed that we donât actually send any parameter names when we call print()
â we say print("Hello")
rather than print(message: "Hello")
.
You can get this same behavior in your own functions by using an underscore, _
, for your external parameter name, like this:
func greet(_ person: String) {
print("Hello, \(person)!")
}
You can now call greet()
without having to use the person
parameter name:
greet("Taylor")
This can make some code more natural to read, but generally itâs better to give your parameters external names to avoid confusion. For example, if I say setAlarm(5)
itâs hard to tell what that means â does it set an alarm for five oâclock, set an alarm for five hours from now, or activate pre-configured alarm number 5?
5. Omitting parameter labels - Additional
- Optional: When should you omit a parameter label?
- Test: Omitting parameter labels
6. Default parameters
6. Default parameters
The print()
function prints something to the screen, but always adds a new line to the end of whatever you printed, so that multiple calls to print()
donât all appear on the same line.
You can change that behavior if you want, so you could use spaces rather than line breaks. Most of the time, though, folks want new lines, so print()
has a terminator
parameter that uses new line as its default value.
You can give your own parameters a default value just by writing an =
after its type followed by the default you want to give it. So, we could write a greet()
function that can optionally print nice greetings:
func greet(_ person: String, nicely: Bool = true) {
if nicely == true {
print("Hello, \(person)!")
} else {
print("Oh no, it's \(person) again...")
}
}
That can be called in two ways:
greet("Taylor")
greet("Taylor", nicely: false)
6. Default parameters - Additional
- Optional: When to use default parameters for functions
- Test: Default parameters
7. Variadic functions
7. Variadic functions
Some functions are variadic, which is a fancy way of saying they accept any number of parameters of the same type. The print()
function is actually variadic: if you pass lots of parameters, they are all printed on one line with spaces between them:
print("Haters", "gonna", "hate")
You can make any parameter variadic by writing ...
after its type. So, an Int
parameter is a single integer, whereas Int...
is zero or more integers â potentially hundreds.
Inside the function, Swift converts the values that were passed in to an array of integers, so you can loop over them as needed.
To try this out, letâs write a square()
function that can square many numbers:
func square(numbers: Int...) {
for number in numbers {
print("\(number) squared is \(number * number)")
}
}
Now we can run that with lots of numbers just by passing them in separated by commas:
square(numbers: 1, 2, 3, 4, 5)
7. Variadic functions - Additional
- Optional: When to use variadic functions
- Test: Variadic functions
8. Writing throwing functions
8. Writing throwing functions
ometimes functions fail because they have bad input, or because something went wrong internally. Swift lets us throw errors from functions by marking them as throws
before their return type, then using the throw
keyword when something goes wrong.
First we need to define an enum
that describes the errors we can throw. These must always be based on Swiftâs existing Error
type. Weâre going to write a function that checks whether a password is good, so weâll throw an error if the user tries an obvious password:
enum PasswordError: Error {
case obvious
}
Now weâll write a checkPassword()
function that will throw that error if something goes wrong. This means using the throws
keyword before the functionâs return value, then using throw PasswordError.obvious
if their password is âpasswordâ.
Hereâs that in Swift:
func checkPassword(_ password: String) throws -> Bool {
if password == "password" {
throw PasswordError.obvious
}
return true
}
8. Writing throwing functions - Additional
9. Running throwing functions
9. Running throwing functions
Swift doesnât like errors to happen when your program runs, which means it wonât let you run an error-throwing function by accident.
Instead, you need to call these functions using three new keywords: do
starts a section of code that might cause problems, try
is used before every function that might throw an error, and catch
lets you handle errors gracefully.
If any errors are thrown inside the do
block, execution immediately jumps to the catch
block. Letâs try calling checkPassword()
with a parameter that throws an error:
do {
try checkPassword("password")
print("That password is good!")
} catch {
print("You can't use that password.")
}
When that code runs, âYou canât use that passwordâ is printed, but âThat password is goodâ wonât be â that code will never be reached, because the error is thrown.
9. Running throwing functions - Additional
10. inout parameters
10. inout parameters
All parameters passed into a Swift function are constants, so you canât change them. If you want, you can pass in one or more parameters as inout
, which means they can be changed inside your function, and those changes reflect in the original value outside the function.
For example, if you want to double a number in place â i.e., change the value directly rather than returning a new one â you might write a function like this:
func doubleInPlace(number: inout Int) {
number *= 2
}
To use that, you first need to make a variable integer â you canât use constant integers with inout
, because they might be changed. You also need to pass the parameter to doubleInPlace
using an ampersand, &
, before its name, which is an explicit recognition that youâre aware it is being used as inout
.
In code, youâd write this:
var myNum = 10
doubleInPlace(number: &myNum)
10. inout parameters - Additional
- Optional: When should you use inout parameters?
- Test: inout parameters
11. Functions summary
11. Functions summary
Youâve made it to the end of the fifth part of this series, so letâs summarize:
- Functions let us re-use code without repeating ourselves.
- Functions can accept parameters â just tell Swift the type of each parameter.
- Functions can return values, and again you just specify what type will be sent back. Use tuples if you want to return several things.
- You can use different names for parameters externally and internally, or omit the external name entirely.
- Parameters can have default values, which helps you write less code when specific values are common.
- Variadic functions accept zero or more of a specific parameter, and Swift converts the input to an array.
- Functions can throw errors, but you must call them using
try
and handle errors usingcatch
. - You can use
inout
to change variables inside a function, but itâs usually better to return a new value.
11. Functions summary - Additional
- Test: Test
Do you remember the two rules of this series? Youâre already being awesome at the first one because you keep coming back for more (you rock!), but donât forget the second: post your progress online, so you can benefit from all the encouragement.