Chapter 4. Functions and Enums: Reusing Code on Demand

image

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.

image

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.

image

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!

  • Images Be defined as a block of code that just does something, and doesn’t need any inputs or have any outputs.

  • Images Take as many inputs of specific types as they need.

  • Images Return as many outputs of specific types as they need.

  • Images Have a variable number of inputs.

  • Images 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:

image

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 and max, 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 and max 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 with min and max.

    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:

image

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:

image

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:

image

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:

image

But we can also independently specify argument labels and parameter names:

image

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)
image

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:

  1. 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)")
    }
  2. 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".

  3. Create a variable

    Create a variable to test the function with:

    var myNumber = 3
    image
  4. Call the function, passing the variable as the value

    Call the multiplyBy42 function, passing the variable myNumber 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".

  5. Check that your variable is unmodified

    Add a call to print, to check that the original myNumber is unmodified:

    print("The value of myNumber is: \(myNumber)")
image

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:

image

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:

image

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)!"
}
image

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:

image

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:

image

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:

image

Fireside Chats

image

Tonight’s talk: What’s in a name?

The Swift Teacher: Student:
So you may have guessed this by now, but did you realize that function names have to be unique?  
  Yes Teacher, that makes sense! Otherwise how could I, or Swift, distinguish between them?
Except that’s actually not quite true...  
  But Teacher! How could it be otherwise? Look—I’ve even tried it just now, and if I use the same function name twice, Swift says I have an “invalid redeclaration.”
That’s because technically, it’s the function’s signature that has to be unique—not its name.  
  Its signature? What’s that?
A function’s signature is a combination of its name, plus its argument names and types, plus its return type (if it has one).  
  Can you give me an example, Teacher?

Certainly. Here are two functions with the same name but with different signatures:

func addString(a: String, to b: String)
func addString(_ a: String, to b: String)
 
  Well, I can see that they’re slightly different, but aren’t the argument names the same?
No! The argument names for the first function are a: and to:, but the argument names for the second function are _: and to:.  
  Huh! That’s right—I had forgotten that the argument name is the same as the parameter name when only one is specified! But you mentioned that the signatures have to be different, and I’m still not sure what that means.
I’m glad you’re paying attention, Student. As I said, the signature is a combination of the function name, the argument names and types, and the return type. So the signature of the first of those example functions is:
addString(a: String, to: String)
 
  OK, and the other one?
addString(_: String, to: String)
So even though the function names are the same, the signatures are different.
 
  Ah—I get it...I think! And parameter names aren’t part of the signature. You mentioned that the return type is part of the signature though. Is that correct?
Yes—in these examples, there is no return value so technically the return type is Void.  
  So I could have another function in the same file with a different signature, like this, and it would be OK?
addString(a: String, to: String) -> Bool
Yes. Since the return type differs from the other two examples, your new function has a different signature, and Swift will still be happy.  
  The penny has dropped, Teacher! So that also means I could write another function like this:
addString(a: Float, to: Float) -> Bool
Absolutely not!  
  Huh? Now I really am confused, Teacher! The signatures are different!
That’s true, but to have a function named addString that takes two Float parameters breaks the unwritten rule of good taste. A function’s name should reflect what it does, or how it interacts with its arguments. And you, Student, should reflect on your lessons!  
  Yes Teacher! You’ll get no argument from me!
STUDENT!  

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:

image

But it’s not just values that are fair game as parameters for your functions. Expressions work as well:

image

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:

image

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:

image
image
image
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.

image

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:

image

Because the manipulateInteger variable can store any function with the same type, you can reassign it:

image

Because this is just a type like any other, you can let Swift infer the function type when you create the variable, too:

image

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:

image
image

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

image

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:

image

You’d then be able to call the function, and access the hello greeting as follows:

image

Fireside Chats

image

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.

image
image

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)

Switching with enums

switch statements and enums are a match made in heaven. You can match individual enumeration values with a switch statement. It’s very useful:

image

An enumeration can have either raw values or associated values, but not both.

Functioncross

image

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!

image

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.

Images Answers in “Functioncross Solution”.

Functioncross Solution

image

From “Functioncross”.

image

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.