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
)