Where technology meets something or other

5 delicious tuple tricks in #swiftlang

There are dozens of reasons to love Swift tuples. Here are five to get you started.

Tuplizing Declarations

Tuples can initialize a bunch of variables or constants at once. For example, here’s a typical set of variable declarations. This code creates several variables and establishes initial values for each.

var x = "XX"
var y = "YY"
var z = "ZZ"

When there’s an underlying semantic relationship between these items, consider placing them into a single line with semicolons. Yes, you can turn your nose up at this but a core relationship is emphasized by reordering the declarations into a one-line presentation. Items are declared together because they have some kind of meaning together:

var x = "XX"; var y = "YY"; var z = "ZZ"

There are, of course, drawbacks. This presentation complicates any modifications you may later need to make to this group: whether inserting or removing variables, or changing their initial values. That’s a bad thing so reserve this approach to items where there is a long-standing logical reason to group.

In fact, you only want to join declarations when there’s some really compelling structural reason these items are grouped together, and you never want to do this with unrelated items. For example, this next snippet is an abomination against Cthulhu:

Avoid this bad example: you are a good person and a good coder, and don’t want Cthulhu to swallow your soul.

Instead, consider a case when there’s a true uniformity of intent. When such exists, you can ditch the semicolons and re-design your declarations to use a tuple, as in the following line of code.

var (w, x, y, z) = ("WW", "XX", "YY", "ZZ")

This line produces the same symbols as the four separate statement snippets you saw previously, but it does so using a simple parsimonious approach. If w, x, y, and z are related, why shouldn’t their declarations be related too?

Tuple declarations become slightly more complicated when the compiler cannot immediately infer typing. In such cases, add explicit typing. For example, 5.0 and 9.6 normally default to double values. You can force these to CGFloat like this:

Even with underlying relationships, avoid complicated declarations that use mixed types. These force you to cognitively match each variable or constant with a type and initial value. This next line is an example of what you don’t want to do:

var (a, b, c) : (Int, Double, CGFloat) = (1, 2, 3)

Don’t force yourself to match a variable from column 1 with a type from column 2 and a value from column 3. It’s bad coding, it’s bad neurocognitive overloading, and Cthulhu doesn’t need that many souls.

Becoming the Tuple

Tuples do more than simplify individual declarations. When looking for targets of tuple opportunity, it’s easy to find instances like these:

In this example, both the generator and item assignments would benefit from tuplification.

There’s also a nagging syntactic issue to consider before conversion. Tuple field numbering starts at 0, not 1. So items.0 is relates to gen1, and items.1 to gen2. This can be ugly if you’re using 1-based numbering. You’ll want to change this when performing your conversion.

Here’s what you get after tuplifying and re-numbering the variables and type parameters:

Adjusting field names

Tuples are basically anonymous structs. As you’ve seen, you get numbered field names for free:

let myTuple = (1, "Hello", 5.2)

gives you

(.0 1, .1 "Hello", .2 5.2)

with each field automatically typed and numbered.

Numbers only take you so far. You can manually add field names to declarations for richer semantics, as in the following example. As you see, “intItem” carries more meaning than “.0”

You can also turn this into a typealias, which you can use to pick up field names:

Unfortunately, this type alias is tied to specific types. You cannot create generic typealiases that refer to structure this way:

typealias PairType<T, U> = (first: T, second: U) // won't work.

That’s a pity. A “Pair” is semantically rich but inherently generic. The answer, at this time, is to work around this with Any. The Any type fixes the declaration issue even if it’s aesthetically displeasing:

Unfortunately, this beautifully named struct does not work with the original mission statement, despite its otherwise sterling qualities:

let gar: Pa = (2, "Hello") // Error!

Swapping values

When it comes to value swaps, you cannot beat the tuple shuffle. Simply construct tuples out of the items whose values you want to reassign, and use tuple-assignment to move those values around. Here’s an example:

(x, y) = (y, x)

You’re not limited to pairs. You can use this approach with as many variables as required:

(x, y, z, w) = (w, x, y, z)

When executed in optimized code, 2-tuple value swaps are as efficient as the built-in swap function, and n-tuple value swaps are as efficient as tmp=first, first=second, second=third,...,last=tmp swaps. It’s a simple, beautiful approach.

Testing for Nil

Bless pattern matching for it is good. Remember this line from the example at the top of the post?

guard items.1 != nil || items.2 != nil else {return nil}

This code tests to see whether both sequences have been exhausted, returning nil from the combined sequences if all items have been used from both incoming generators. Checking multiple items against nil is a pretty common Swift task, and it’s one that really benefits from tuples.

In this example, there’s a far better approach than looking individually at each tuple field. Pattern matching enables you to leverage an if-case statement to determine whether a tuple contains a .Some case or not. Here’s what that test looks like:

if case (.None, .None) = items {return nil}

The if-case here “binds” the tuple field items against the tuple in the case. It uses a single equal sign even though there is no actual assignment here. When items is (nil, nil), the generator returns nil.

This statement is kind of an odd bird. The condition looks like you could use it as a boolean intermediate for, for example, a ternary condition:

return case (.None, None) = items ? nil : items // nope! won't work

But this is not legal Swift and will not compile. An if-case performs pattern matching binding and exposes any newly bound symbols in its scope clause. This tuple-test uses a byproduct of binding to add early exit but it’s a bit off-label if you get my meaning.

Part 6 of 5: Better Return Values

Sebastian Celis writes, “My personal favorite way to use tuples is when a function really wants to return more than one piece of data. I prefer the tuple to being forced to use an inout parameter”

He’s right. There is never a good reason for you to return multiple distinct values with in-out parameters. Use proper error handling if needed and return a tuple instead.

Wrap-Up

So there you have it. Five really cool ways to use tuples, and this write-up doesn’t even begin to touch on the ways you can use tuples in switch statements.

Tuples are one of the big paradigm-shift areas in Swift (along with algebraic data types, pattern matching, value types, protocol oriented programming, functional programming, etc) but tuples tend to get short shrift in terms of people paying proper attention to them.

Hopefully this post will give them a little of the love they deserve and raise your awareness of their awesomeness and possibilities. Remember: Swift isn’t just about re-writing code using new syntax. It’s about embracing new ways of thinking about and architecting your code.