Enums and Switches

OCaml programmers rejoice–you'll be back in 3110. For everyone else, don't worry this won't be anything too special (until the advanced sections).

In programming, you will sometimes need to represent a set of states. There are multiple ways to do this, but you will most often see it implemented as a subset of integers that correspond to a specific state–usually denoted in constants. Below we will model what college someone is in using said technique:

let artsAndSciences = 0
let engineering = 1
let ag = 2
let hotel = 3 
...

let noah = Student(name: "Noah", college: artsAndSciences)
// noah: Student
//     name: String = "Noah"
//     college: Int = 0

This works fine if done properly. There is a concern here that the college field is technically Int; we can pass any integer into the college field but it only makes sense if it is an integer that is contained in the constants predefined. For example, the following code would break our example:

let brokenStudent(name: "Cornell Redditor", college: -1)
// brokenStudent: Student
//     name: String = "Cornell Redditor"
//     college: Int = -1

We don't have a -1 college, so if we ever use this object later, we will have difficulty discerning what that means. In python, we would fix this with assert statements and/or preconditions. This is fine and works in Swift too, but we have a better alternative. Instead of using integers to represent a data type, let's just make a new data type! This is where enums come; an enumeration type; a type where all valid states are distinguished.

If we are creating a basic enum, we use the syntax:

enum <Insert Type Name> {
    case State1
    case State2
    ...
}

So for our college example, we could do something like...

enum College {
    case artsNSciences
    case engineering
    case agAndLifeSciences
    case hotel
    ...
}

We can think of each of these states as constructors for a state, so the state constructors are a member of the type itself. In lay-man terms, we can access a state like this:

let myCollege = College.artsNSciences
// myCollege: College = College.artsNSciences

If we are assigning a state somewhere that expects an enum, we can forgo the type. I.e. if we are assigning to a variable or a function, that already has the college type (it is explicitly typed instead of inferred) we just write .<state>:

let myCollege: College = .artsNSciences
// myCollege: College = College.artsNSciences

let noah = Student(name: "Noah", college: .artsNSciences)
// brokenStudent: Student
//     name: String = "Cornell Redditor"
//     college: College = College.artsNSciences

Switch Statements

Another great reason to use enums is switch statements–a very simple version of pattern matching. You can also think of this as a simplification for doing a lot of if-else statements. We will examine how to use switch-statements through an example:

With our students defined in the previous section, let's say we are trying to figure out their graduation requirements. We will build a function that takes in a school, and returns the requirements. For sake of laziness, if the school is one not explicitly listed above, we will just return the swim test. Here's how we would do this with if-else statements:

func getRequirements(_ college: College) -> [String] {
    if college = .artsNSciences {
        return ["Foreign Language", "Biological Sciences", ...]
    } else if college = .engineering {
        return ["Physics", "Chemistry", ...]
    } else if college = .agAndLifeSciences {
        return ["I don't know"]
    } else if college = .hotel {
        return ["Probably something hotel-related"]
    } else {
        return ["Swim test"]
    }
}

This is fine and dandy, but there's a lot of writing, I have to make sure my if-else statements are correct, and I often forget cases when doing more complicated things like this. We can use switch statements to fix this. To use a switch we use the syntax:

switch <Insert parameter> {
    case someCase:
     <code to execute>
    case anotherCase:
     <code to execute>
    ...
    default:
     <code to execute>
}

So, for the above example, we would write it like:

func getRequirements(_ college: College) -> [String] {
    switch college {
    case artsNSciences: return ["Foreign Language", "Biological Sciences", ...]
    case .engineering: return ["Physics", "Chemistry", ...]
    case .agAndLifeSciences: return ["I don't know"]
    case .hotel: return ["Probably something hotel-related"]
    default: return ["Swim test"]
}

Notice that we use default to catch cases that have not been handled.

Switch statements can work for many types, but they work especially well with enums because they have such clear patterns that can be handled in unique manners. Moreover, if we ever update our enum to include additional states, switches can let us know when we do not cover this case.

Literals

You want to associate (sorry this is a poor word choice–you'll see why later) your enums with certain values. For example, with the College example, we may want to print the college someone is going to:

func printCollege(_ student: Student) {
    print("\student.college")
}
printCollege(student: Student(name: "Noah", college: .artsNSciences))
// "artsNSciences"

It's cool that Swift will allow us to print the state name directly, but seeing as we named our college kind of gross abbreviations, this doesn't necessarily make sense. If want to change this, we can have our enum conform to a type, and directly assign values.

enum College: String {
    case artsNSciences = "Arts & Sciences"
    case engineering = "Engineering"
    case agAndLifeSciences = "Agricultures & Life Sciences"
    case hotel = "Hotel Administration"
    ...
}

If you ever need to access these literal values, you can use the rawValue property:

College.artsNSciences.rawValue
// "Arts & Sciences": String 

You can also use raw values as an initializer:

let someCollege = College(rawValue: "Arts & Sciences")
// someCollege: College = College.artsNSciencessw

Advanced Topics

Enums with Associated Values are a very powerful tool that mimic OCaml types. This can be hard to employ and is not very helpful for UI until you get to Networking!

Indirect enums are for those that want to Functional. This is a very weird, cool, powerful, and crazy tool. Not really sure when you'd ever use this in UI though ¯_(ツ)_/¯.

Last updated