Unit 19: Enumerations & Switch Flashcards
Case
in an enum, the keyword declares a name for one of the enum’s options; conversely, in a switch, the case keyword introduces a pattern to try to match a value against
Default
selected when no other option is available
Enum
a keyword that declares a type made up of a group of related choices; an instance of an enum will be exactly one of the enum’s choices; the keyword “enum” originated from the term “enumeration” = listing distinct things one by one
Switch
a keyword that chooses between different courses of action based on some value’s characteristics
the options for the values are written in case statements
once the switch statement finds the first match between the value and a case, the block of code next to the case statement is executed
if no case statements match, the default statement’s code is executed
once one of the blocks has been executed, the programme continues running the next code after the end of the entire switch statement
Multiple Choice
You’re often given choices to make in life. In many cases, you must pick one of a set of options, and picking nothing isn’t a choice.
For example, in the school cafeteria on a given day, the lunch options might be pasta, a burger, or soup. The cafeteria staff are in no mood for you to mix and match, so you must make a choice.
Pick one.
This system works well for the cafeteria, and for the school:
The staff know that they’ll only need to provide one of three meals.
The staff can prepare and serve each meal quickly and efficiently.
No time is wasted understanding or taking down special orders.
The school knows that everyone has had a nutritious lunch and will be energized for an afternoon of learning.
As a programmer you can benefit from applying these same kinds of controlled choices in your programs.
Making Decisions, Revisited
Consider the lunch options from the previous page. If you were writing a function to model the cafeteria, you might do this:
func cookLunch(choice: String) -> String { if choice == "pasta" { return "🍝" } else if choice == "burger" { return "🍔" } else { return "🍲" } } cookLunch(choice: "pasta") //: - experiment: Ask for some different choices by calling `cookLunch(choice:)` a number of times. Ask for anything you can think of. What result do you get back?
/*: This function has the following drawbacks:
- If you ask for anything other than exactly
"pasta"
or"burger"
, you get soup. - There’s nothing telling you what you can ask for. If you can’t see the body of the function, all you know is that it takes a
String
, but it doesn’t tell you any of the strings it might expect.
There’s a better way to deal with situations like this.
[Previous](@previous) | page 2 of 21 | [Next: Enumerations](@next) */
Enumerations
In Swift, you can use an enumeration to represent a group of related choices. Each choice is called a case. You can define your own enumeration types, just as you can define your own structs:
enum LunchChoice { case pasta case burger case soup } /*: The declaration above creates a new type, `LunchChoice`. Instances of `LunchChoice` can only be one of the three defined cases.
An enumeration is usually called by its abbreviation, enum.
The name of an enum starts with a capital letter, like all other type names.\
The name of a case starts with a lower-case letter, like the names of properties and methods.
The name of the enum should be singular, as in LunchChoice
, not LunchChoices
, because the value refers to only one choice, not many choices.
You make instances like this: */ let choice = LunchChoice.burger //: One benefit of an enum is it limits the choices to one of its cases. You can’t order off-menu.\ //: Uncomment the line below to see the error, then comment it out again when you’re done: //let special = LunchChoice.fish /*:
- experiment: Create some constants yourself for different cases in the enum. Notice how autocompletion shows you the possible options.
- /
/*: Next learn about how the type system understands enums.\
Enums and Type Inference
Swift can save you some typing when it expects a particular type of enum.
Here’s the LunchChoice enum from the previous page. It’s written a little differently. To save space, it includes multiple cases on a single line, separated by commas:
enum LunchChoice { case pasta, burger, soup } /*: On the previous page you made an enum instance like this:
let choice = LunchChoice.burger
This variable has a type annotation: */ var choice: LunchChoice //: If Swift already knows what type to expect, you can skip the enum name. Since you’ve already specified the type of `choice`, you can leave out the enum name when assigning a value: choice = .burger //: - experiment: Practice assigning other values to `choice` using this shorter dot notation. Notice that the autocompletion menu pops up once you type the period.
/*: Next, learn when it makes sense to use an enum.\ [Previous](@previous) | page 4 of 21 | [Next: When to Use Enums](@next) */
When to Use Enums
Whenever you have a restricted group of related values in your code, it might be good to think about using an enum.
If there are no restrictions on the value, or you have a large number of possible values, enums probably aren’t a good fit.
Imagine you’re writing an app, a fun little sports game. You’re using a struct to represent each player on the field. Each player has the following properties:
name
skillLevel
team
position
name would be a String. You wouldn’t use an enum here because there are too many possibilities.
skillLevel would be an Int, since the game uses a point-style system as the user gains skill.
team would be an enum. There are only two teams on the field: .red and .blue.
position would also be an enum: .quarterback, .seeker, .pitcher, and so on, depending on how you design the game.
Define enums to represent the team and position options. Check on the previous pages for a refresher on the syntax.
Comparing Enums
To make decisions using enums, you need to be able to compare one value to another.
Here’s the LunchChoice enum you’ve seen before:
enum LunchChoice { case pasta, burger, soup } /*: You can compare enum values using `==`, just as you have with values of the types `String` and `Int`: */ let myLunch = LunchChoice.burger let yourLunch = LunchChoice.burger
if myLunch == yourLunch { "We're having the same for lunch!" } else { "Can I try your lunch?" } //: - callout(Excercise): Change `myLunch` to a different choice to see a different value in the results sidebar. //: //: Next, make a better version of the `cookLunch` function using enums.\ //:
Enums and Functions
Enum values can be used as parameters or return values for functions, just like any other type.
Here’s the LunchChoice enum you’ve been working with:
enum LunchChoice { case pasta, burger, soup } //: You could rewrite the `cookLunch` function from earlier: func cookLunch(_ choice: LunchChoice) -> String {
if choice == .pasta { return "🍝" } else if choice == .burger { return "🍔" } else { return "🍲" } }
cookLunch(.burger) //: - experiment: Call the function a few more times, passing in different food choices.
/*: Using the `LunchChoice` enum instead of a string solves the issues that this function had when it took a `String` value. There was no way to know what was on the menu.
When calling the function, you know that you have to pass in a LunchChoice
. Autocompletion will tell you exactly what the options are. You can’t pass in anything that’s not on the list, so you’ll always get what you’re looking for.
But the function could still be better.
The Problem with If
An if statement is useful for checking a single condition. But when if statements are used to check multiple conditions using else if, they can start to get unwieldy.
The code ends up being visually “noisy,” with a lot of repetitious text that doesn’t add new information.
The animation below shows an if statement with an enum. You can see that a lot of text is repeated and the enum cases get a little lost in the rest of the code:
The animation highlights another problem with the if statement: The last choice isn’t really anything else, it’s soup. If you were reading this code without knowing the last case in the enum, you’d have to guess.
Rewriting the function to use each specific case doesn’t help the situation much:
enum LunchChoice {
case pasta, burger, soup
}
func cookLunch(_ choice: LunchChoice) -> String { if choice == .pasta { return "🍝" } else if choice == .burger { return "🍔" } else if choice == .soup { return "🍲" } return "Erm... how did we get here?" } cookLunch(.soup) /*: You still need the final `return` statement. Otherwise the function causes an error because it can’t be sure you’ve covered all the possible cases in the if statements.
- experiment: Comment out the final
return
statement to see an error. Uncomment it again, and try to change the value passed in tocookLunch
so that the finalelse
statement is called.\
(Hint: How would you get an enum value that didn’t match anything in the if statement?)
Apparently if statements aren’t a great fit when dealing with enums. So what is?
[Previous](@previous) | page 8 of 21 | [Next: Switch](@next) */
Switch
You’ve seen that if statements aren’t a great fit for checking enum values.
They add a lot of visual noise, and they can’t tell that you’ve covered all of the cases, even though the point of enums is to provide a limited list of cases.
What’s a better way to choose different courses of action based on the value of an enum?
enum LunchChoice { case pasta case burger case soup }
let choice = LunchChoice.burger /*: The answer is a _switch_ statement: */ switch choice { case .pasta: "🍝" case .burger: "🍔" case .soup: "🍲" } /*: The switch statement looks very much like the enum declaration above. That’s because they’re designed to work well together.
The switch statement starts with the keyword switch
, followed by the value that it’s checking and an opening brace:\
switch choice {
\
Next you add a series of cases to be checked, each with the case
keyword, followed by a value and a colon:\
case .pasta:
\
Since the type of the enum is known, you can use the dot syntax and leave out the type name.\
If the value being checked matches the case statement, the code between the matched case and the next case is run. Then the switch statement, just like the if statement, is done.
Next, find out some other features of switch statements.
[Previous](@previous) | page 9 of 21 | [Next: Exhausting the Possibilities](@next) */
Exhausting the Possibilities
Switch statements have a special feature: they must be exhaustive. This means a switch statement must exhaust every possibility of the value being checked. With an enum, you can use a different case to handle every possible value.
enum LunchChoice { case pasta case burger case soup }
let choice = LunchChoice.burger
switch choice { case .pasta: "🍝" case .burger: "🍔" case .soup: "🍲" } /*: - callout(Exercise): Add another case, `taco` to the enum. What happens to the switch statement?
You see the error Switch must be exhaustive
. You’re not allowed to write a switch statement that doesn’t cover every case.
- callout(Exercise): Fix the error by adding another case to the switch statement. Use the other cases as a guide. You can bring up the emoji picker using Control-Command-Space or copy this one: 🌮
The fact that switch statements must be exhaustive means that you can be sure that one of the cases will match the value you’re testing. This feature prevents you from accidentally missing a value. It also alerts you if you update the definition of an enum without updating any switch statements that use it.
[Previous](@previous) | page 10 of 21 | [Next: The Default Case](@next) */
The Default Case
This enum is used to represent how good something is:
enum Quality {
case bad, poor, acceptable, good, great
}
let quality = Quality.good //: The switch statement is a little different to the ones you’ve seen up to now: switch quality { case .bad: print("That really won't do") case .poor: print("That's not good enough") default: print("OK, I'll take it") } /*: The switch statement doesn't have a case for every possible value of the enum. Instead, there is a `default` keyword which will be used if no other matches are found. This is similar to the final `else` clause when using an if statement.
- experiment: Change the value of
quality
to test when the default case is used, and when specific cases are used.\
\
Try adding more cases to the switch statement. Notice that thedefault
case has to be the last case in the switch statement.\
\
Try adding more cases to the enum.
If you add a default case to your switch statement, you won’t get an error when you add new cases to the enum. Can you think of a way this this could lead to an unexpected error?
On the next page, find out another way to match several cases.
[Previous](@previous) | page 11 of 21 | [Next: Multiple Cases](@next) */