Functions
Declarations
Functions in Swift are nothing special. We start them with the keyword func
followed by the function name and then the arguments enclosed in parenthesis: func (args)
.
Parameters should be formatted (name: type). When arguments are assigned to parameters, they become constants. We can get around this within the function by assigning the parameter to another variable–just be conscious of this if you're getting mutating-constant errors.
Finally, if there is a return type, you should put -> <return type>
after the parameters. If there is no return type, you either leave out the arrow or write void
for the return type–this will be more important when we talk about closures, otherwise, you can basically forget about Void.
func <name>(parameter1: type1, ... parameterN: typeN) -> <return type> { body }
Alright let's start with some basic functions!
First, a function that returns the string "Hello World!":
Fun little side note, you have a one-line function, you can omit the return
statement because the compiler will just assume that the expression is being returned.
Okay, let's spice things up by adding some arguments. We'll add a function that prints out someone's name (given their name).
Calling Functions
Calling a function is pretty easy. All you have to do is <function name>(parameter: value...)
Let's call our previous functions
Anonymous Arguments
You may have noticed that Swift is different than some other languages because you need to include the parameter name. We can change this, per parameter, by making them anonymous. You do this by adding a wildcard (_
) in front of the parameter name in the function header:
Functions as Values
Swift functions are higher order meaning that they can be passed and stored as values! You may have noticed the type annotations in the previous code samples... And to store a function in a variable you're gonna need to understand their types... time to get comfortable with them! Side note, the notation I use above is actually slightly different because it acknowledges parameter names.
These types are not hard. For the arguments we write a tuple of the argument types. This is then followed by an arrow ->
and then the return type (you need void here if there is no return type). For example, helloWorld
, which is helloWorld() -> String
can be stored in a variable like
let aGreeting: () -> String = helloWorld
Similarly, greeting, which was greeting(name: String) -> Void
can be stored like:
let anotherGreeting: (String) -> Void = greeting
Now that we have these functions in variables, we can call them by enclosing their arguments in parenthesis, separated by commas and ignoring parameter names:
Exception Handling
If a function has a possibility of raising an exception directly (not an assertion error, fatal error, or runtime error) we must state this in the function, so that callers can handle the error. To do this, we write throws
before the arrow pointing to the return type: func someFunction() throws -> Void
.
If a function takes a function as an argument that has the potential of throwing an error, this new function must deal with it. If we are just going to throw the error again, we use the rethrows
tag instead.
func someFunction(anotherFunctionThatThrows: () throws -> Void) rethrows -> Void
This is very rare, so you don't really need to worry about it.
Closures
We can also create functions directly as anonymous functions. You will need to store it somewhere, and if it is going into a variable, you will need an explicit type. If it is directly into a function as an argument, your type will already be specified.
Arguments are given names by default–they are named $
+ whatever their index is in the arguments, so the first is $0
, the second is $1
and so on. But these are not pretty, so sometimes you may choose to rename them. To do this, at the beginning of your closure write your names separated by commas followed by the keyword in
. It's like your saying "these variables are in the rest of the code".
Escaping
This is an advanced concept (you also don't need to understand it until the end of the course).
If a function (the main function) takes a function (the inner function) as an argument, and if the inner function is not done resolving by the time the main function returns, then we have a problem. The inner function is being stored in the main function, so if we erase the main function (because it is done being executed), then we cannot execute the inner function because it will be gone! To fix this, we change the type declaration of the inner function to be tagged @escaping
. This will handle this problem for us. I think the compiler will tell you when you mess this up, but I won't guarantee that, so if you're playing with higher-order functions keep it in mind.
Partial Application
Partial application is a method of creating new functions by applying some arguments to a previous function. Partial application in Swift is somewhat manufactured–the experience is not as wonderful, magical, amazing... etc. as in OCaml. In many functional langauges, you can just apply a few arguments and it will automatically apply it for you. In Swift, we have to be more intentional. To curry, in Swift, you have to write a new function that applies it for you. Let me show you what I mean. Let's say we have some function curry
of type(Int, Double, String) -> String
. Let's say we want to do partial application with an Int
of 3 and a String
of "Martha". We create a new function that takes in the remaining arguments, applies them, but also applies 3 and "Martha".
Last updated