Swift's Modern Solutions to Old Code Problems

28 Mar 2018

About a year ago, Christian Maioli wrote about 4 “forgotten” code constructs, suggesting that they may still be useful in your programs. The constructs are: goto, multiple inheritance, eval, and recursion. In this post, I will show you how Swift solves some of the same problems using more elegant concepts.

Being a compiled language, eval isn’t and will likely never be supported. Recursion, on the other hand, is supported and a great way to solve many problems and fits very well with Swifts other functional programming features. This leaves particular uses of goto and multiple inheritance.

There are two advantages to using goto here. The first advantage is that it keeps the code “flat”. By the time you get to the call for second_operation(), you know that first_operation() was a success. Had first_operation() failed, you would have skipped ahead to first_error.

The Swift way of doing this is the guard...else statement. It takes a conditional and a code block. If the conditional is not met the code block is executed. Furthermore, code block must exit the containing block. This means that any code after the guard...else statement is guaranteed that the condition is met.

Unlike the goto solution, the Swift code makes the purpose of the code very clear to anyone reading it. The compiler too, will understand what you’re trying to do and will give you an error if you fail to exit the scope inside the else block. Here’s an example of what the Swift code might look like:

While you do have to do something to exit the guardExample() function within the else block, return is not the only way of doing that. You could also throw an error or call a function that “never returns”, like the fatalError(). The Swift compiler is smart enough to recognize that and guarantee it at compile time.

Using defer to clean up

There’s another benefit to the goto example that Christian wrote in C. I will start by explaining how and why C programmers use goto for this sort of error handling, then I’ll show you the Swift alternative.

Imagine that the function you were writing had to load data from a large XML file into a database server. The call to first_operation() allocates some memory to store the XML data and second_operation() opens a database connection. After you’ve done your processing, you will want to close the database connection and free up the memory taken by the XML data.

This clean up code typically goes at the end of the function, say, first_cleanup() and second_cleanup(). The problem is, what if you get an error opening the database and you have to bail out? You will want to clean up the XML data only. The problem becomes more complicated every time you add another resource that requires cleanup. Keeping track of what’s been allocated so far and remembering to deallocate it for each error scenario can quickly turn into a nightmare.

The way C programmers organized functions like this is by having all of the clean up code at the end of the function. Deallocation of each allocated resource is performed in reverse order. If at the start of the function you allocate XML data first and open a database second, then at the end of the function you would close the database first you deallocate XML second. Each clean up section appears under a different goto label. That’s why in Chiritian’s code, second_error: appears beforefirst_error:.

You can think of allocation/deallocation pairs like layers of an onion. The deeper you get into the function, the more layers of deallocation you have to swim back through to get out safely. This applies regardless of whether the function succeeds or fails.

In Christian’s function, even if there were no errors in first_operation() and second_operation(), the code under second_error: and first_error: still gets executed. However, if you get an error on first_operation(), you will skip the middle part of the function including the second_error: part and only do the last bit of clean up required (if any), under first_error:.

Swift has a very elegant solution to this particular problem, using defer blocks. Simply put, whenever you allocate resources that you want to be cleaned up eventually, you put the clean up code in a defer block. Swift takes care of the rest.

In the above code, if the function exits for any reason after the first defer block, only freeXML(xml) will be called. If the function returns after the second defer block, then both disconnectFromDatabase(db) and freeXML(xml) will be called, in that order. The great thing about this code is that the clean up code appears immediately next to the allocation code. Once you’re done with that chunk of code, you don’t have to think about it again. Swift takes care of remembering to run the defer blocks for you, as needed, and in the correct (reverse) order.

Repetitive code inside functions

The example that Christian uses here doesn’t fully demonstrate the benefit of goto, but we will stick to it. Just imagine that there’s a lot more code where it currently says return true; or return false;:

As Christian points out, the same effect can be easily and more cleanly achieved by using function calls instead of labeled sections of code where errors will not get detected. Calendar logic is hard enough as it is!

Swift lets you define functions within functions, so you can have a set of helpers to use to prepare your return values. Those helper functions will remain scoped to the enclosing function so you don’t have to worry about polluting the global namespace. Furthermore, the inner function can access variables in its enclosing function. This allows you to create an entire sophisticated world inside of a function that remains isolated from the world outside the function, accessible only through the functions type signature.

To illustrate those features in Swift, I’ve written a more complex example. The function qifRecords parses a QIF file into a list of transactions, where each transaction is an array of fields. QIF is an old file format but it’s still widely used by banks for exporting financial transactions. In this file format, each line is a field encoding some data about a transaction. A transaction consists of a number of consecutive fields (lines). Transactions are separated from each other by the caret ^. Here’s the code for this function:

Notice how functions like addToCurrentRecord and addRecord operate on currentRecord and records, which are defined in the enclosing function, qifRecords. In many other languages, you might implement this as a class to get similar facilities, perhaps implementing those helper functions and variables as private members of the class. However, I find a function like this much more elegant at the call site: you give it an argument and it returns the value you’re looking for. If it were a class, you’d probably instantiate it, passing the source string to the initializer, call a single function on this class named something like parse() to get the value you’re looking for. You don’t need that instance anymore. It’s a poor fit for a class, in my opinion.

Protocols instead of multiple inheritance

Another feature Christian talks about is multiple inheritance. After pointing out a few caveats, this is where he feels multiple inheritance fits:

On the other hand, sometimes you want to inherit from parents that are completely separate from each other. This is where multiple inheritance can become productive. Classes that have orthogonal behaviors are the best case scenario for multiple inheritance. [emphasis in original]

Followed by this code example:

classFlyer:passclassSwimmer:passclassFlyingFish(Swimmer,Flyer):pass# can swim and fly

Swift does not have multiple inheritance but supports protocols instead. A protocol defines a collection of methods, getters, and setters. Your concrete data types (e.g., classes, structs, or enums) can then declare that they conform to a protocol. The compiler will make sure that they adhere to the protocols requirements. The Swift standard library and community makes extensive use of protocols to support this type of composition between types. Protocols are analogous to interfaces or traits in other programming languages.

I have rewritten the example above and expanded the Flyer protocol a bit to demonstrate usage:

protocolFlyer{varairspeedVelocity:Int{get}varposition:Int{getset}}protocolSwimmer{}classFlyingFish:Swimmer,Flyer{// can swim and flyletairspeedVelocity=4varposition:Int=0}

As a statically typed language, Swift requires that everything has a known type at compile time. However, in many situations, you can use a protocol as the type, as opposed to a specific concrete type. Imagine, for example, that you have a type representing a “body of water” which contains a list of everything swimming in it. You can define it like this:

structBodyOfWater{letname:Stringletswimmers:[Swimmer]}

Now if you have other kinds of Swimmers, like Tuna or Frog, you can insert them in that list. On the other hand, if you try to put an Int or a Banana in there, the Swift compiler will complain.

You can take protocols a step further in Swift by adding functions with default implementations to a protocol. We can add a function fly that takes a duration and uses that combined with the airspeedVelocity to move the position of the Flyer by the correct amount:

protocolFlyer{staticvarairspeedVelocity:Int{get}varposition:Int{getset}mutatingfuncfly(time:Int)}extensionFlyer{mutatingfuncfly(time:Int){position+=Self.airspeedVelocity*time}}protocolSwimmer{}classFlyingFish:Swimmer,Flyer{// can swim and flystaticletairspeedVelocity=4varposition:Int=0}

This is possible because we know that any type conforming to Flyer will be guaranteed to have an airspeedVelocity and a position. This is one of the most powerful features in Swift. It allows you to conform your type to a protocol, implement the minimum required feature set, and get a much richer interface through default implementations implemented in terms of that minimal feature set.

Another advantage of protocols in Swift is that you can conform a type to a protocol through a type extension, without modifying the original type definition. This allows you to conform a type made by someone else to an arbitrary protocol. Imagine using a date/time library that has a Date type and an ORM that allows you to store data in a database as long as it conforms to a protocol called DatabaseValue. You could take the Date type and extend it to add conformance to the DatabaseValue. What’s remarkable is that Date and DatabaseValue could come from two different libraries that know nothing about each other.

All of that (and more) without having to constantly worry about whether or not you’re using multiple inheritance correctly and all of the issues that come with multiple inheritance such as ambiguities arising from complex ancestral trees. Protocols are flat: there’s no inheritance and therefore, no trees.

Final Thoughts

Swift’s approach to add language features is to consider not only the benefits that can be derived from adding them but also the potential harm. By forcing ourselves to minimize the potential for harm, we can come up with more elegant solutions that maximize power while minimizing the potential for complexity, misunderstandings, and errors.

I should note that those features are not unique to Swift. Swift borrows heavily from other programming languages from a variety language paradigms, leading to a very rich set of features.