Tuples

Tuples are fixed-size collections that can store values of different types. They're perfect for grouping related data.

Tuple Basics

Tuples group multiple values into a single compound value. Unlike arrays, tuples can contain mixed types and have a fixed size.

Creating Tuples

// Pair (2 elements)
let pair = (42, "hello")

// Triple (3 elements)
let triple = (1.5, true, "world")

// Mixed types
let person = ("Alice", 30, 5.6)

// Explicit type annotation
let point: (int, int) = (10, 20)

// Single element (still a tuple)
let single = (42,)  // Note the comma

// Nested tuples
let nested = ((1, 2), (3, 4))

Key Difference: Arrays require all elements to be the same type. Tuples allow mixed types but have a fixed size.

Tuple Type Syntax

Tuple types are written as comma-separated types in parentheses:

Type Description Example
(int, int) Pair of integers (10, 20)
(string, int) String and integer ("age", 25)
(float, float, float) Triple of floats (1.0, 2.0, 3.0)
(bool, string, int, float) Four different types (true, "ok", 1, 2.5)
((int, int), string) Nested tuple ((5, 10), "point")
// Explicit type annotations
let coordinates: (float, float) = (3.14, 2.71)
let person_info: (string, int, bool) = ("Bob", 35, true)
let stats: (float, float, float, float) = (1.0, 2.0, 3.0, 4.0)

Accessing Tuple Elements

Access tuple elements using dot notation with zero-based indices.

Positional Access

let person = ("Alice", 30, 5.6)

// Access by position
let name = person.0     // "Alice"
let age = person.1      // 30
let height = person.2   // 5.6

// Use in expressions
let age_in_months = person.1 * 12  // 360
let greeting = "Hello, " + person.0  // "Hello, Alice"

// Nested access
let coords = ((10, 20), "point A")
let x = coords.0.0      // 10
let y = coords.0.1      // 20
let label = coords.1    // "point A"

Note: Tuple indices are fixed at compile time. You cannot use variables to index tuples like you can with arrays.

Working with Tuple Values

// Store tuple in variable
let point = (100, 200)
let x = point.0
let y = point.1

// Pass tuple elements to function
fn distance(x1: int, y1: int, x2: int, y2: int) -> float {
    let dx = x2 - x1
    let dy = y2 - y1
    return sqrt(float(dx * dx + dy * dy))
}

let p1 = (0, 0)
let p2 = (3, 4)
let dist = distance(p1.0, p1.1, p2.0, p2.1)  // 5.0

Tuples as Return Values

Tuples are commonly used to return multiple values from functions.

Returning Multiple Values

// Return quotient and remainder
fn divide_with_remainder(a: int, b: int) -> (int, int) {
    let quotient = a / b
    let remainder = a % b
    return (quotient, remainder)
}

let result = divide_with_remainder(17, 5)
let q = result.0  // 3
let r = result.1  // 2

// Return min and max
fn min_max(a: int, b: int) -> (int, int) {
    if a < b {
        return (a, b)
    } else {
        return (b, a)
    }
}

let bounds = min_max(10, 5)
print("Min: " + str(bounds.0))  // 5
print("Max: " + str(bounds.1))  // 10

Complex Return Types

// Return multiple statistics
fn calculate_stats(numbers: [float]) -> (float, float, float, float) {
    let sum = 0.0
    let min_val = numbers[0]
    let max_val = numbers[0]

    let i = 0
    while i < len(numbers) {
        let sum = sum + numbers[i]
        if numbers[i] < min_val {
            let min_val = numbers[i]
        }
        if numbers[i] > max_val {
            let max_val = numbers[i]
        }
        let i = i + 1
    }

    let mean = sum / float(len(numbers))
    return (mean, min_val, max_val, sum)
}

let data = [1.5, 2.5, 3.5, 4.5, 5.5]
let stats = calculate_stats(data)
print("Mean: " + str(stats.0))
print("Min: " + str(stats.1))
print("Max: " + str(stats.2))
print("Sum: " + str(stats.3))

// Return weights and bias
fn initialize_layer(input_size: int, output_size: int) -> (Tensor, Tensor) {
    let weights = tensor_randn([input_size, output_size])
    let bias = tensor_zeros([output_size])
    return (weights, bias)
}

let layer_params = initialize_layer(784, 128)
let weights = layer_params.0
let bias = layer_params.1

Tuples as Parameters

Functions can accept tuples as parameters to group related data.

// Function accepting tuple
fn print_point(point: (int, int)) {
    print("Point: (" + str(point.0) + ", " + str(point.1) + ")")
}

let p = (5, 10)
print_point(p)

// Function with multiple tuple parameters
fn calculate_distance(p1: (float, float), p2: (float, float)) -> float {
    let dx = p2.0 - p1.0
    let dy = p2.1 - p1.1
    return sqrt(dx * dx + dy * dy)
}

let point1 = (0.0, 0.0)
let point2 = (3.0, 4.0)
let distance = calculate_distance(point1, point2)  // 5.0

// Complex tuple parameter
fn process_person(info: (string, int, float, bool)) {
    let name = info.0
    let age = info.1
    let salary = info.2
    let is_active = info.3

    print("Name: " + name)
    print("Age: " + str(age))
    print("Salary: " + str(salary))
    print("Active: " + str(is_active))
}

let person = ("Alice", 30, 75000.0, true)
process_person(person)

Common Use Cases

Coordinates and Points

// 2D coordinates
let point2d: (float, float) = (3.5, 7.2)
let x = point2d.0
let y = point2d.1

// 3D coordinates
let point3d: (float, float, float) = (1.0, 2.0, 3.0)
let x = point3d.0
let y = point3d.1
let z = point3d.2

// Named context with tuples
fn create_point(x: float, y: float) -> (float, float) {
    return (x, y)
}

fn translate_point(p: (float, float), dx: float, dy: float) -> (float, float) {
    return (p.0 + dx, p.1 + dy)
}

let origin = create_point(0.0, 0.0)
let moved = translate_point(origin, 5.0, 10.0)  // (5.0, 10.0)

Configuration and Settings

// Training configuration
let train_config: (float, int, int) = (0.01, 100, 32)
let learning_rate = train_config.0  // 0.01
let epochs = train_config.1          // 100
let batch_size = train_config.2      // 32

// Model hyperparameters
let model_config: (int, int, int, float) = (784, 128, 10, 0.001)
let input_size = model_config.0
let hidden_size = model_config.1
let output_size = model_config.2
let dropout_rate = model_config.3

// Function using config tuple
fn create_model(config: (int, int, int, float)) -> (Tensor, Tensor) {
    let input_size = config.0
    let hidden_size = config.1

    let w1 = tensor_randn([input_size, hidden_size])
    let b1 = tensor_zeros([hidden_size])

    return (w1, b1)
}

let weights = create_model(model_config)

Results and Metrics

// Training results
fn train_epoch(data: Tensor, labels: Tensor) -> (float, float, int) {
    let loss = 0.5  // Simplified
    let accuracy = 0.95
    let samples = 1000

    return (loss, accuracy, samples)
}

let results = train_epoch(data, labels)
let epoch_loss = results.0
let epoch_accuracy = results.1
let num_samples = results.2

print("Loss: " + str(epoch_loss))
print("Accuracy: " + str(epoch_accuracy))
print("Samples: " + str(num_samples))

// Evaluation metrics
fn evaluate_model() -> (float, float, float, float) {
    let precision = 0.92
    let recall = 0.89
    let f1_score = 0.90
    let accuracy = 0.91

    return (precision, recall, f1_score, accuracy)
}

let metrics = evaluate_model()
print("Precision: " + str(metrics.0))
print("Recall: " + str(metrics.1))
print("F1: " + str(metrics.2))
print("Accuracy: " + str(metrics.3))

Key-Value Pairs

// Simple key-value storage
let entry1: (string, int) = ("age", 25)
let entry2: (string, float) = ("score", 95.5)
let entry3: (string, bool) = ("active", true)

// Function working with key-value
fn print_entry(entry: (string, int)) {
    print(entry.0 + ": " + str(entry.1))
}

print_entry(("height", 175))  // height: 175

// Array of tuples
let entries: [(string, int)] = [
    ("apples", 5),
    ("oranges", 3),
    ("bananas", 7)
]

let i = 0
while i < len(entries) {
    let item = entries[i]
    print(item.0 + ": " + str(item.1))
    let i = i + 1
}

Nested Tuples

Tuples can contain other tuples, allowing for complex data structures.

// Tuple of tuples
let rectangle = ((0, 0), (10, 20))
let top_left = rectangle.0      // (0, 0)
let bottom_right = rectangle.1  // (10, 20)
let x1 = rectangle.0.0          // 0
let y1 = rectangle.0.1          // 0
let x2 = rectangle.1.0          // 10
let y2 = rectangle.1.1          // 20

// Complex nested structure
let person_data = (
    ("Alice", 30),           // name and age
    (5.6, 140.0),           // height and weight
    (true, "Engineer")      // is_active and job
)

let name = person_data.0.0          // "Alice"
let age = person_data.0.1           // 30
let height = person_data.1.0        // 5.6
let weight = person_data.1.1        // 140.0
let is_active = person_data.2.0     // true
let job = person_data.2.1           // "Engineer"

// Function returning nested tuple
fn create_bounding_box(x: int, y: int, w: int, h: int) -> ((int, int), (int, int)) {
    let top_left = (x, y)
    let bottom_right = (x + w, y + h)
    return (top_left, bottom_right)
}

let bbox = create_bounding_box(10, 20, 50, 30)
print("Top-left: (" + str(bbox.0.0) + ", " + str(bbox.0.1) + ")")
print("Bottom-right: (" + str(bbox.1.0) + ", " + str(bbox.1.1) + ")")

Tuples vs Arrays

Understanding when to use tuples versus arrays:

Feature Tuples Arrays
Size Fixed at compile time Can vary at runtime
Element Types Can be different Must be same type
Access Dot notation: tuple.0 Index: array[0]
Use Case Related but different data Collection of same type
Iteration Access each field individually Loop with index variable
// Use tuple: Fixed structure, mixed types
let person: (string, int, float) = ("Alice", 30, 5.6)

// Use array: Variable size, same type
let scores: [int] = [85, 92, 78, 95, 88]

// Tuple for function return
fn get_dimensions() -> (int, int, int) {
    return (1920, 1080, 24)  // width, height, depth
}

// Array for collection
fn get_measurements() -> [float] {
    return [1.5, 2.3, 3.7, 4.2]
}

Best Practices

DO:

  • Use tuples to return multiple values from functions
  • Use tuples for fixed-size, heterogeneous data
  • Keep tuple size reasonable (2-5 elements)
  • Add comments explaining what each position represents
  • Use descriptive variable names when extracting values
  • Consider tuples for coordinates, key-value pairs, configs

DON'T:

  • Create very large tuples (>5 elements) - hard to track positions
  • Use tuples when a struct would be clearer (if structs existed)
  • Nest tuples too deeply (>2 levels)
  • Use tuples for homogeneous collections (use arrays instead)
  • Forget that tuple positions are fixed - document their meaning

Documentation Pattern

When using tuples, add comments to clarify element meanings:

// Good: Document tuple structure
fn get_stats() -> (float, float, float) {
    // Returns: (mean, min, max)
    return (5.0, 1.0, 10.0)
}

// Good: Use descriptive names
let stats = get_stats()
let mean = stats.0
let min_val = stats.1
let max_val = stats.2

// Better: Comment the tuple type
let person: (string, int, float) = (
    "Alice",  // name
    30,       // age
    5.6       // height
)

Next Steps