Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Tuples

Tuples group a fixed number of values into one value.

The fields in a tuple can have different types.

A tuple is a good first step for a small value with a fixed shape. When the shape needs names but still behaves like plain data, use a named tuple. When the value needs methods, invariants, or a lifecycle, move to a class.

A tuple has a fixed shape. If you need a small value with exactly two or three fields, a tuple is often a good fit. If you keep forgetting what .0 and .1 mean, switch to a named tuple so the fields explain themselves. If the data starts needing methods or construction rules, switch to a class.

actor main(env):
    pair = ("Ada", 36)
    point = (x=3, y=4)

    print(pair.0)
    print(pair.1)
    print(point.x)
    print(point.y)

    env.exit(0)

Access positional tuple fields with .0, .1, and so on.

Named tuples use field names such as .x and .y.

Returning tuples from functions

Tuples are handy when a function naturally returns a small fixed group of values.

def parse_result():
    return (ok=True, code=200)

Acton can infer the tuple shape here from the returned value.

Comparing tuples and using them as keys

Tuples whose components support it can be compared and hashed, so they work as dictionary and set keys and can be sorted.

def demo() -> str:
    d = {(1, 2): "a", (3, 4): "b"}       # tuples as dict keys
    positions = [(2, "b"), (1, "c")]
    print((1, "x") == (1, "x"))          # True
    print((1, 2) < (1, 3))               # True: comparison is lexicographic
    print(sorted(positions))             # [(1, 'c'), (2, 'b')]
    return d[(1, 2)]

actor main(env):
    print(demo())
    env.exit(0)

Acton derives equality, ordering, and hashing for tuple types from their components. Type inference can use those derived tuple protocols when it solves ordinary code, including dictionary keys and set elements. For example, if a dictionary key is later used as a tuple with k.0 and k.1, Acton can infer the tuple key type without an explicit dictionary annotation.

Equality, ordering, and hashing apply componentwise, so they require each component to support the operation in turn: (int, str) can be compared because int and str can, but a tuple containing a function cannot.

Both sides of a comparison must have the same shape: the same number of components, and for named tuples the same field names in the same order. Comparing a positional tuple with a named one is a type error. Named tuples with reordered fields can be compared after converting to a common annotated type:

a : (x: int, y: int) = (y=2, x=1)        # reorders the fields
print((x=1, y=2) == a)                   # True

Named tuples are the bridge between raw tuple positions and classes. They keep the value lightweight while making the shape self-documenting. Because the tuple shape is part of the type, changing field count or names is an API change.