8.5. Tuples

A tuple is a way of grouping multiple values with potentially different types into an aggregate data structure. Tuples are similar to structs, except that a tuple’s fields are indexed instead of named. Tuples are often used to return multiple values from a function or procedure. Any storable type may be stored within a tuple, including arrays of any rank (a matrix is the rank-2 case), vector, string, struct, and other tuple types, nested to any depth (subject to the acyclicity rule). Only streams may not be stored in a tuple.

8.5.1. Declaration

A tuple value is declared with the keyword tuple followed by a parentheses-surrounded, comma-separated list of types. The list must contain at least two elements; a tuple type with fewer than two members is ill-formed, and the compiler must emit a TypeError (see Errors). As with any other type, a tuple variable is mutable only when declared var (see Type Qualifiers). For example:

var tuple(integer, real, integer[10]) t1;
tuple(character, real, character[256], real) t2;

Note that while each tuple declaration defines a new type, the tuple type is not named explicitly. Rather, it has a type signature (T1, T2, ...), where T1, T2 are the types of its members. The number of fields in a tuple must be known at compile time. This includes instances of type inference, where a variable is declared without an explicit type signature using var or const. In this case, the variable must be initialized immediately with an expression whose type is known at compile time.

8.5.2. Access

The elements in a tuple are accessed using dot notation. Dot notation can only be applied to tuple variables: applying it to a non-variable – the result of an expression, or a tuple literal – must emit a TypeError (see Errors), exactly as for struct field access. Dot notation means an identifier followed by a period and then a literal integer.

Field indices start at one, not zero. Because a tuple index must be a literal, an index less than one or greater than the tuple’s number of fields is caught at compile time, and the compiler must emit an IndexError (see Errors). For example:

t1.1
t2.4

Tuple access can be used either to retrieve the element value for an expression or to assign a new value to the element.

y = x + t1.1;     // Allowed
t1.1 = type-expr; // Allowed

8.5.3. Literals

A tuple literal is constructed by grouping values together between parentheses in a comma separated list. For example:

tuple(integer, character[5], integer[3]) my_tuple = (x, "hello", [1, 2, 3]);
var our_tuple = (x, "hello", [1, 2, 3]);
const your_tuple = (x, "hello", [1, 2, 3]);
tuple(integer, real, integer[10]) tuple_var = (1, 2.1, [i in 1..10 | i]);

8.5.4. Operations

The following operations are defined on tuple values. In all of the usage examples tuple-expr means some expression yielding tuples with the same type signature, while int_lit is an integer literal as defined in Integer Literals and tuple-inst is the name of a tuple instance as defined in Identifiers.

Class

Operation

Symbol

Usage

Access

dot

.

tuple-inst.int_lit

Comparison

equals

==

tuple-expr == tuple-expr

not equals

!=

tuple-expr != tuple-expr

Note that in the above table tuple-inst always refers to a variable for Access. Accessing a literal could be replaced immediately with the value inside the tuple literal; however, tuple-expr may refer to a literal in comparison operations to enable shorthand like this:

if ((a, b) == (c, d)) { }

Comparisons are performed pairwise. Two tuples are equal when for every expression pair, the equality operator returns true. Two tuples are unequal when one or more expression pairs are unequal. Comparing two tuples of different type signatures with no common implicit-cast target must emit a TypeError (see Errors); two signatures that differ but share a common implicit-cast target compare legally after a two-sided implicit cast (for example (1.0, 2) == (2, 3.0) – see Tuple to Tuple). This table describes how the comparisons are completed, where t1 and t2 are tuple yielding expressions including literals:

Operation

Meaning

t1 == t2

t1.1 == t2.1 and ... and t1.n == t2.n

t1 != t2

t1.1 != t2.1 or ... or t1.n != t2.n

Operator precedence and associativity are specified once, for all types, in the table of operator precedence.

8.5.5. Unpacking

Any tuple expression may be assigned (unpacked) into multiple lvalues. If the size of the tuple being unpacked does not match the number of lvalues being assigned, the compiler must emit an AssignError (see Errors). There is no partial unpacking of tuples.

var real a;
var real b;
a, b = (3.14, 1.5);

8.5.6. Type Casting and Implicit Casts

To see the types that tuple may be cast and/or implicitly cast to, see the sections on Type Casting and Implicit Casts, respectively.