Understanding Optionals

Dejan Agostini
Understanding Optionals
Last week we covered some basics on Optionals. This week we'll dive in a bit deeper. We'll take a look at the implementation of the optionals, optional chaining, nil-coalescing operator and a few more goodies. Granted, you'll probably do just fine knowing the basics, but it's fun to know how things work. And by the end of this article you'll see that there's nothing magical about optionals.

'Optional' enum

Optionals are nothing more than enums with two cases .none and .some. Of course, the .some case has an associated value. Optionals are so widely used in swift that there's a lot of syntactic sugar around them. For example, if you wanted to define on optional Int, you can do it like this:
SwiftDefining Optionals
var longOptional: Optional<Int>
var shortOptional: Int?
Here we can see that the '?' operator is actually a shorthand version of 'Optional<Wrapped>'. If we dive a bit deeper and go to the definition of the 'Optional<Wrapped>' we'll see that it's just an enum. It has two main cases, like so:
SwiftDefinition of Optional
public enum Optional<Wrapped> : ExpressibleByNilLiteral {
    case none
    case some(Wrapped)
}
Of course this means that we can use the enum cases everywhere in the code. For example, if we wanted to define a function that returns an optional Int, we could write it like this:
SwiftReturn Optional Int
func returnLongOptional(_ input: Int) -> Optional<Int> {
    if input % 2 == 0 {
        return .some(input)
    } else {
        return .none
    }
}
There's a shorter way of writing the same function, like so:
SwiftReturn Int?
func returnShortOptional(_ input: Int) -> Int? {
    if input % 2 == 0 {
        return input
    } else {
        return nil
    }
}
Your .none case gets replaced by nil, and the associated value gets 'extracted' automatically. Because of all this syntactic sugar, you probably won't use the 'Optional' enum directly in your day-to-day development.

Optionals With 'switch' Statement

Since optionals are actually enums, it's only natural that we can use a switch statement with optionals. You could, for example, do some custom filtering like this:
SwiftSwitching on Optionals
func optionalsWithSwitch(_ input: Int?) -> String {
    switch input {
    case 0?: return "Zero"
    case (1..<15)?: return "A small number"
    case let number?: return "Your number: \(number)"
    case nil: return "I got nothing"
    }
}
Pretty cool, right. Let's move on to optional chaining.

Optional Chaining

Let's say you have a class that has properties of an optional type. For example, your object graph might look something like this:
SwiftSimple Object Graph
class Engine {
    var name: String?
}

class Car {
    var engine: Engine?
}

class CarFactory {
    var productionLine: Car?
}
If you need to access the 'name' property of the engine using optional binding, things might get a bit verbose:
SwiftAccess property using optional binding
let engine = Engine()
engine.name = "Lucy"

let car = Car()
car.engine = engine

let factory = CarFactory()
factory.productionLine = car

if let car = factory.productionLine, let engine = car.engine, let name = engine.name {
    print("engine name: ", name)
} else {
    print("didn't get the engine name")
}

// prints: engine name:  Lucy
While this is not as bad as the earlier versions of swift, it can still be a bit hard to read. Luckily there's a better way of doing this using optional chaining:
SwiftOptional Chaining
if let name = factory.productionLine?.engine?.name {
    print("chained name: ", name)
}

// prints: chained name:  Lucy
We're unwrapping optional values here using the '?' operator. If at any point the unwrapping operation returns nil the optional binding will fail and the 'if let' block won't get executed. This is definitely a lot more readable.

Nil-coalescing operator

This is just a fancy way of saying 'default value' when working with optionals :) Let's say you're trying to unwrap an optional and if it's nil, you want to use a default value. You have a few options. The most obvious, and the most verbose would be something like this:
SwiftDefault Value with Optional Binding
var engineName = "Default"
if let name = factory.productionLine?.engine?.name {
    engineName = name
}
print(engineName)

// prints: Lucy
You might say, well, we have a ternary operator. We could use it, but we wouldn't improve on the readability:
SwiftTernary Operator
var engineName = factory.productionLine?.engine?.name != nil ? factory.productionLine!.engine!.name! : "Default"
print(engineName)

// prints: Lucy
Nil-coalescing operator, the '??' operator, gives us a much shorter and readable code:
SwiftNil-coalescing operator
var engineName = factory.productionLine?.engine?.name ?? "Default"
print(engineName)
If you dive in the swift optional type, you'll find the function definition for the '??' infix operator:
Swift?? function
public func ??<T>(optional: T?, defaultValue: @autoclosure () throws -> T) rethrows -> T
The actual implementation is:
Swift?? function implementation
public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T)
    rethrows -> T {
  switch optional {
  case .some(let value):
    return value
  case .none:
    return try defaultValue()
  }
}
You might have noticed that the default value is actually a closure. This is an optimisation to make sure that the default parameter doesn't get evaluated unless it's actually used. The parameter is marked with @autoclosure which automatically wraps an argument to the function in a closure, thus removing the need for us to use the curly braces when we call the function.

map and flatMap

Value transformations are so common that we have a few functions built-in to the optional type just for this purpose. You're probably used to seeing map and flatMap functions when working with collections, but you can also find them when working with optionals. Let's say you wanted to use your optional to create another value of a different (or same) type, something like this:
SwiftConvert Optional
let possibleNumber: Int? = Int("42")
var slightlyBiggerNumber: Double?

if let number = possibleNumber {
    slightlyBiggerNumber = Double(number) * 1.5
}
print(slightlyBiggerNumber)

// prints: Optional(63.0)
Here we're parsing a string into an Int and then multiplying that Int by 1.5 if it's not nil. We can use the map function to write a shorter version of this:
SwiftUsing map
let possibleNumber: Int? = Int("42")
let slightlyBiggerNumber = possibleNumber.map { Double($0) * 1.5 }
print(slightlyBiggerNumber)
// prints: Optional(63.0)
As we can see, this is much shorter and more readable. flatMap is doing pretty much the same thing as map. The only difference being that flatMap will flatten the result. Meaning, if your result is an optional optional it will be flattened to an optional. Let's examine this on an example:
SwiftDouble Optional
let possibleNumber: Int? = Int("42")
let mappedNumber = possibleNumber.map { Int("\($0 + $0)") }
print(mappedNumber)
// prints: Optional(Optional(84))
Because map was returning an optional, and we parsed a string into an Int, which also returns an optional, we get a double optional. We can easily get rid of this with a flatMap:
SwiftUsing flatMap
let possibleNumber: Int? = Int("42")
let mappedNumber = possibleNumber.flatMap { Int("\($0 + $0)") }
print(mappedNumber)
// prints: Optional(84)
Now our result is nice and flattened. And this brings us to the end of this post.

Conclusion

Optionals are a crucial part of swift, and you won't be able to escape them if you want to use the language. Understanding how they work will only make you a better developer. Last week we covered some basics on optionals that will cover most of your day-to-day development needs. But sometimes you just need a little more, I hope you learned a little bit more reading this article. Have a nice day :) Dejan.

More resources