Tuples and Named Tuples

by Martin D. Maas, Ph.D

Tuples in Julia are fixed-length collections of values, of potentially different data types, which can't be modified once initialized. Tuples are closely related to function arguments.

Initializing Tuples

Tuples can be created in a similar way to arrays, replacing square brackets with parenthesis. For example,

t = (3.14, 2.72)

Creates a variable with type Tuple{Float64, Float64}, whose values can be accessed via t[1] and t[2].

In fact, we don’t even need the parenthesis to initialize a tuple, and simply doing

t = 3.14, 2.72

will work just as well.

Tuples in general can’t be modified one initialized. For example, attempting to do:

t[1] = 2*t[1]

will result in the following ERROR: MethodError: no method matching setindex!(::Tuple{Float64, Float64}, ::Float64, ::Int64).

However, this doesn’t mean we can’t operate with tuples. Tuples can be copied, converted to arrays, and more.

For example, we can overwrite the value of a tuple with another tuple:

t = (2*t[1], t[2])

This also allows us to apply a broadcasting operation, like what we can do with arrays:

t = t .* 2

Converting Tuples to Arrays

Tuples can be converted to arrays in various ways: using the collect function, array comprehensions, or the splat notation.

a = (1, 2, 3)

t1 = collect(a);
t2 = [x for x in a];
t3 = [a...];           

all producing the same output:

3-element Vector{Int64}:

Destructuring Tuples

We can cast the elements of a tuple as different variables in a very straightforward manner:

pi_approx, e_approx = t

Tuples are, for this reason, a convenient return type for a function that seemingly returns multiple outputs, such as we discussed in the post about functions.

Named Tuples

An interesting variant is that we can assign names to the different elements of a tuple. For example, by declaring

p = ( x = 1.1, y = 2.4)

The elements of p can be accessed via p.x and p.y as well as p[1] and p[2].

p is of type NamedTuple{(:x, :y), Tuple{Float64, Float64}}.

We can also retrieve the names of the keys and the values of a named tuple, using the keys and values functions, as follows:

K = keys(p)                 # (:x, :y)
V = values(p)               # (1.1, 2.4)

and we can merge this key/value pairs into a named tuple again with the zip function:

p_new = (; zip(K,V)...)     # (x = 1.1, y = 2.4)

Merging Named Tuples

An other interesting operation we can do with Named Tuples is to merge them.

For example, let’s say that we have some parameters defined in a Named Tuple:

TemporalParams = ( 
    Δt = 0.1, 
    T = 2

and we then compute some extra quantity which we then would like to pack together into a similar tuple, we can then do

SpatialParams = ( 
    Δx = 0.05, 
    a = 0,
    b = 100

TotalParams = merge(TemporalParams, SpatialParams)

Destructuring Named Tuples

Since Julia 1.7, there is a new sytnax that can be used to destructure a NamedTuple:

(; y, x) = p

So, let’s say that we have passed the complete list of parameters to a certain function, but we only want to use a few of them, we can then do something like

(; a,b,Δt ) = TotalParams

This will create new symbols a,b,and Δt, without the need to constantly reference them like TotalParams.a,TotalParams.b,TotalParams.Δt.

Also note that the parameters can be destructured regardless of their order.

This is a very cool feature I personally use a lot to deal with different kinds of parameters.