Previously, we learned to unwrap an optional with if let. That let us use the non-optional value inside a block of code. In Swift 1, that's all we had. Very soon, advanced developers complained about some esoteric phrases like "pyramid of doom", and "preferring early exit". Let's see an example by writing a simple function that takes 4 optional arguments and works with their values

  func use(value0:String?, value1:String?, value2:String?, value3:String?, )->String? {

  }

Now we'll add code which checks whether each of these values actually exists, has unique error code for each one, and finally executes some code if all the values are non-nil.

  func use(value0:String?, value1:String?, value2:String?, value3:String?) {
    if let value0:String = value0 {
           if let value1:String = value1 {
             if let value2:String = value2 {
                 if let value3:String = value3 {
                    //code involving all 4 values
            print( value0 + value1 + value 2 + value3)
                  } else {
                      // error condition specific to lacking value3
                  }
              } else {
                  //error condition specific to lacking value2
                  return
              }
          } else {
            //error condition specific to lacking value1
          }
      } else {
          //error condition specific to lacking value0
          return nil
      }
  }

Now, if you're looking at this block of code and thinking, "I have no idea what this does or why it's written this way." Great! That's the idea. This is an anti-pattern.

The "pyramid of doom" is the white space along the left side of the function. It’s "base" is the left edge, and it’s "peak" is around //code involving all 4 values. See that? Once you do, you can’t un-see it. Notice, also, that we have specific code associated with failures of the specific values. The error code for the first value is down at the bottom of the function, and after the code that would run if we passed the test! This takes the developer’s mind out of context, and makes it more difficult to reason about the errors. Yes, you could figure out what happens in this function, but why waste the energy? We prefer something called "early exit". In other words, when a precondition for execution of the function fails, return before doing the work.

Swift supports "early exit" with the keyword guard. Like if let, guard let evaluates an Optional and declares a new value which is non-optional. Like if let, we declare an else block, and we must declare an else block, and what’s more, we can do whatever we want inside the else block, as long as we exit scope. Unlike an if let, guard extends the new value’s scope to the remainder of the existing scope, so my pyramid of doom turns into this:

  func use(value0:String?, value1:String?, value2:String?, value3:String?) {
    guard let value0:String = value0 else {
      //error condition specific to lacking value 0
      return
    }
    guard let value1:String = value1 else {
      //error condition specific to lacking value 1
      return
    }
    guard let value2:String = value2 else {
      //error condition specific to lacking value 2
      return
    }
    guard let value3:String = value3 else {
      // error condition specific to lacking value3
      return
    }

    //code involving all 4 values
    print(value0 + value1 + value 2 + value3)
  }

No more pyramid scheme. The code for handling each precondition failure is visually associated with that check that determines the failure. Our code very clearly talks about each condition that needs to happen before regular execution can take place, and then provides non-optional values for the regular code.

Inside the else block, "exit scope" means break or continue if we're inside a repeat, while or for loop. For a function, it means return.

There's another optimization we can make, both if and guard support the idea of multiple conditions, separated by commas.

  guard let value0:String = value0
  ,let value1:String = value1
  ,let value2:String = value2
  ,let value3:String = value3
  else {
    // error condition for any missing value
    return
  }

And our else block now responds in a generic way to any errors, instead of specifically to each one. Not so esoteric now, is it? "Prefer early exit." It's actually the difference between legible code and completely-insanely unfathomable code. Which one do you want to read?

I know, you're asking why I put the commas at the beginning of the lines. It has to do with years of merge conflicts from adding items to lists. Fortunately, Swift is smart enough to know what we're doing.

Consider for a moment that our conditions are a bit stronger than that we must have a non-nil value for each string. What if value1 and value3 must not be "empty", i.e. a zero-length String?

  guard let value0:String = value0
  ,let value1:String = value1
  ,!value1.isEmpty
  ,let value2:String = value2
  ,let value3:String = value3
  ,!value3.isEmpty
  else {
    // error condition for any missing value
    return
  }

Swift lets us freely mix in Bool and let expressions in any order in an if or guard. They are implicitly combined with "and". And they support shortcuts. In other words, if value1 was empty, Swift wouldn't get around to trying to unwrap value2.

Let's continue with some other ways to control flow.

??

Suppose that we have an Optional and our code needs a definite value, perhaps by providing a default value to replace nil.

  let userPreferredFileExtension:String?

Here we have a String? to represent the user's choice of a file extension, which will be nil if they do not provide one. But my file writing code demands having a file extension, so I need to use a default if the user didn't specify one. Swift provides a nifty ?? operator:

  let fileExtension:String = userPreferredFileExtension ?? ".txt"

The ?? operator checks if the preceding value is nil and returns it if it is not. If it is nil, it continues with the next expression. In this case, we've used the ?? to guarantee that we will have a String so any additional code can treat fileExtension as non-optional. I think you'll find using the power of optionals and ?? simplifies tons of code that uses caches and defaults.

while

Swift also has a while statement, which is the same as C while statement, with the same changes we had for an if.

  var a:Int = 0
  while a < 3 {
    print("a == \(a)")
    a += 1
  }
  //a == 0
  //a == 1
  //a == 2

Note that we've used the += operator, because Swift doesn't have a ++ or -- operator. Yeah, some folks complained when they removed ++, but it didn't take long to do a project wide search and replace with += 1, so they moved on.

There is also a repeat-while, which is the Swift version of C's do-while. The do keyword in Swift is used for error handling, and we'll cover that in a future week. I would give you an example of repeat-while, but I can't confirm that I've ever actually written one; so I'm going to skip it.

Summary

Today we learned how to use guard-let-else to provide early exit where conditions are not ripe for our regular code to continue. We also learned about providing default values in place of Optionals. Last, we learned about while loops.

Supporting early-exit with Optionals? Now that's Swift!