Why I like Swift
Part 1. Optionals
BLUF:
Optionals are unique variables types, chosen because they can handle particular situations with greater accuracy. They can convey: (1) there is a value for this variable (and, here it is), or, (2) we have a situation where it doesn’t make since for this variable to have a value – therefore, this variable doesn’t have a value.
Details
For any type in Swift, (String, Int, Float, etc.), an optional variable is declared by appending a ? onto the end of the type.
var anOptionalString: String? = “my optional string” var anOptionalInt: Int?
The first line can be read as: declare a variable, anOptionalString, of type Optional String and assign it the value “my optional string”. On the second line, no assignment is made; we let anOptionalInt default to the value of nil. Optionals can be assigned nil at any time, even after it has been assigned a value.
print( “ \(anOptionalString) “) // prints: // Optional(“my optional string”) print(“ \(anOptionalString!) “) // force unwrap value using ! // prints: // my optional string
Here, we attempt to print anOptionalString, but it doesn’t print the pure string we expect. Its value needs to be unwrapped. On line 5 we force unwrap the value with a exclamation mark at the end of the variable. It works, but unwrapping an optional by force is not a good practice; if the optional has no value, the program will crash. We’ll look at better ways to unwrap optional values in a bit.
Behind the scenes, an optional is implemented as a two-cased enum. The two cases are some and none. In the case of some, the enum holds “associated data” that matches the enum’s backed-data-type; none cases return nil. Optionals are so common in Swift, there are several forms of syntactic sugar that safely check for set cases, and retrieve associated data in the same construct. For example:
Nil coalescing operator: ??
var pbsNodeTypeString: String? = ":bigmem=1" let blankString = "" // var declares a variable, let declares a constant print( "select=1:ncpus=24:mpiprocs=24\(pbsNodeTypeString ?? blankString)" // prints select=1:ncpus=24:mpiprocs=24:bigmem=1 // set up for pbs SELECT statement for standard job ... pbsGpuNodeString = nil print( "select=1:ncpus=24:mpiprocs=24\(pbsNodeTypeString ?? blankString)" // prints select=1:ncpus=24:mpiprocs=24
The nil coalescing operator, used within the print statements on lines 5 and 11, says: if the optional to the left has a value, unwrap it and use it here, else, use the value to the right in this place.
Of course, we could have accomplished this with a regular String, and simply set it to hold a blank string for a standard pbs job. We’ll look at a better example ahead.
Advantages of Optionals
Optionals are able to hold more information than a simple data value. Since they can be set or unset, they can imply a boolean value, answering a yes/no question. Optional variables can say: “Yes, there is a value here, and here’s my associated data”, or “No, there’s no value here“. The question it answers has to do with the variable.
struct HistoricalPerson { var name: String var birthDate: String var deathDate: String? . . . }
Consider the variables birthDate and deathDate in the struct above. Anyone who is a HistoricalPerson has a birthDate, so that variable can be a normal String. But not every HistoricalPerson has a deathDate (yet). It’s more accurate to let that variable be an Optional String. As an optional, deathDate is able to declare: “yes, there is a value, and this is it” or else “no, there is no value here – a value in this case would not make sense – as this HistoricalPerson is still living.”
To use the True/False nature of Optionals, Swift provides the syntactic sugar of an if let statement.
var somePastPOTUS = HistoricalPerson() // set the struct properties for the historical POTUS if let date = somePastPOTUS.deathDate { // code block if date receives an associated value print("death date was \(date)") } else { // code block when somePastPOTUS.deathDate is nil print(" \(somePastPOTUS.name) is still living") }
The if let construct above, can be read as:
If (we can) let date equal the associated data of the Optional String variable somePastPOTUS.deathDate, then execute the first block of code, with date set to that value – else execute the second block of code with no assignment to the date variable.
Other Syntactic Sugar for Optionals
Guard statement.
guard let unwrappedDate = somePastPOTUS.deathDate else { // code block to clean up, and transfer program control return } // code resumes pending guard statement executed normally
A guard statement ensures the line will execute properly, or else will enter a code block to perform corrective actions and finally will transfer program control with either a return, break, continue or throw statement. Often a simple return statement is seen as the entire code block.
The Argument for Optionals
Optionals eliminate many runtime errors. For Example, when requesting a dictionary retrieval with a non-existent key. (Swift dictionaries always return optionals, since this is possible, forcing non-existent keys to be dealt with.)
Optionals Can Solve Ambiguous Meaning
- Suppose you get a return of -1 from a function called: getGreatestIntInList( list ). Is -1 the greatest value in the list? Or was this an empty list ? Changing the function to return an optional Int? would solve this problem.
- Suppose you get a return of 0 from a function called: getIndexOfFirstSubStringInString(subString, String). Does that mean the substring occurs immediately in the String, or that no subString exists in the String? Using an optional Int? return value would solve this problem.
Solves Doesn’t-Exists Values
- var spouseName: String? // handles not-married cases
- var flightTimeFromCityAToCityB: Int? // handles cases where no single flight exists from City A to City B
- var pbsRequiredQueueNameForQsubType : String? // handles cases when the QsubType is defined instead by a pbsQsubTypeString appended onto the end of the SELECT statement
- var pbsQsubTypeString: String? // handles cases when the QsubType is defined instead by establishing a dedicated queue for these jobs to run in.
Simplifies logic
- var valueOfOneAndOnlyFaceUpCard: CardRank? // A neat way to know that the condition-of-interest is met, and the card rank when that condition is met. We’re able to directly code for that situation directly from inspecting the variable. Also, if we find no cards are face-up, or more than one card is face-up, we can set this variable to nil to better describe the state of the game.
Recap
Can we survive without Optionals in a programming language? Of course we can. Still, there are advantages of having Optional types that Swift brings to the table.
Optionals remind us that there are times when the value of nothing can be an appropriate and valid. Some programming languages touch on this, or have ambiguous workarounds, but Swift directly supports this concept through Optional data types.