A Tuple By Any Other Name

| | 0 Comments| 7:10 PM

Apple’s new Swift language provides support for a common data structure known as the tuple. Unlike the venerable array and dictionary, however, what precisely a tuple is and how it behaves is different among programming languages. What do we mean by this?

In Python you can get the number of elements in a tuple:

$ python
Python 2.7.6 (default, May 30 2014, 22:21:15)
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.34.2)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> currentConditions = ("Mostly Cloudy", "90", "9")
>>> len(currentConditions)

In Python you typically cannot access the tuple elements by name, but do so by index:

>>> currentConditions[0]
'Mostly Cloudy'

There is a mechanism for creating so-called named tuples in Python using the collections module.

You can iterate over the elements in the tuple in Python:

>>> for e in currentConditions:
... print e
Mostly Cloudy

Erlang provides a tuple datatype similar to that of Python: you can create arbitrary groups of data bundled together. In our previous example all of the elements in the tuple were of the same type, namely a string. This does not have to be the case. In Erlang:

7> CurrentConditions = {"Mostly Cloudy", 90, 9}.
{"Mostly Cloudy",90,9}

Neither Python or Erlang allow you to change a given element member in the tuple, but for different reasons. In Erlang you can’t change the value of a variable once its been set, whereas in Python tuples are immutable (cannot change) types. To get around that in Erlang one might do:

11> UpdatedConditions = setelement(2,CurrentConditions,89).
{"Mostly Cloudy",89,9}

In Python the following is effective:

>>> cc = list(currentConditions)
>>> cc[1] = '89'
>>> currentConditions = tuple(cc)

Erlang does provide named elements in a special type of tuple called a record.

Now let’s turn to Swift tuples. We prefer using the REPL feature of Swift from the command line rather than using the Playground. See our previous post if you aren’t familiar with using Swift from the terminal. Of course, you can also follow along in the Playground.

$ swift -repl
Welcome to Swift! Type :help for assistance.
1> var currentConditions = ("Mostly Cloudy", 89, 9)
currentConditions: (String, Int, Int) = {
0 = "Mostly Cloudy"
1 = 89
2 = 9

The first thing we notice is how Swift takes the tuple created and shows you what the constituent datatypes are. In this case our currentConditions variable is a (String, Int, Int) tuple. We can access the constituent members using a dot syntax notation, which is in contrast to the bracket syntax of Python:

2> currentConditions.0
$R1: String = "Mostly Cloudy"

Unlike Python, Swift tuples support named elements by default (that is, no need to use any special imports). This is handy for treating a tuple like a lightweight struct, commonly used in C and C++.

var currentConditions:(conditions:String, temperature:Int, wind:Int) = ("Mostly Cloudy", 89, 9)
currentConditions: (conditions: String, temperature: Int, wind: Int) = {
conditions = "Mostly Cloudy"
temperature = 89
wind = 9

With this variable definition we can do this:

2> currentConditions.temperature
$R1: Int = 89

Also unlike Python, Swift tuples are mutable and we can change the constituent elements:

3> currentConditions.temperature = 87
4> currentConditions
$R3: (conditions: String, temperature: Int, wind: Int) = {
conditions = "Mostly Cloudy"
temperature = 87
wind = 9

What is not mutable is the datatype of a given element. That is, we cannot assign a String to an element that is defined as an Int:

5> currentConditions.temperature = "87"
:5:31: error: cannot convert the expression's type '()' to type 'Int'
currentConditions.temperature = "87"

Tuples can also contain optionals. Observe:

1> var currentConditions:(String?,Int?,Int?)
currentConditions: (String?, Int?, Int?) = {
0 = nil
1 = nil
2 = nil
2> currentConditions.0="Partly Cloudy"
3> currentConditions
$R2: (String?, Int?, Int?) = {
0 = "Partly Cloudy"
1 = nil
2 = nil

If we wanted to uppercase the current condition string we would have to apply the exclamation mark to our index:

5> currentConditions.0!.uppercaseString
$R4: String = "PARTLY CLOUDY"

or we could use unwrapping:

10> if let conditions = currentConditions.0 {
11. println(conditions.uppercaseString)
12. }

So far, Swift tuples appear to be more robust and useful than Python tuples. Well, before we make the assumption that they are, let’s try obtaining the number of elements in the tuple with the global countElements function:

5> countElements(currentConditions)
:5:1: error: cannot convert the expression's type '$T3' to type 'ForwardIndex'

Oops. Unlike Python, you cannot obtain the number of elements in the tuple. Okay, then let’s iterate over the tuple:

6> for e in currentConditions {
7. println(e)
8. }
:6:10: error: type '(conditions: String, temperature: Int, wind: Int)' does not conform to protocol 'Sequence'

Denied. Swift’s pithy reply “does not conform to protocol ‘Sequence'” is a obtuse way of saying you can’t iterate over it. If you have a background in C or C++ these things should not come as a shock. It wouldn’t have occurred to you to “get the length” of a struct or iterate over it. A struct is not an array or a list, silly!

Our conclusion? While Swift tuples are useful, particularly for returning multiple values from a function or grouping together related data, they are not a replacement for arrays or proper structures and classes. You cannot iterate over them or add any setter logic to a named element. For example, consider a tuple defined as (name:String,birthday:String,age:Int). In reality age should be a calculated property of a Person, not a named element of a tuple. There’s nothing necessarily wrong about a tuple defined as such, but if you find yourself with tuples that you want to modify after they are set, or you want to iterate on the elements, you probably are using the wrong datatype.

Leave a Reply

Your email address will not be published. Required fields are marked *