ZetCode

F# tuples

last modified May 17, 2025

Tuples in F# are lightweight data structures that group together multiple values, possibly of different types. They are immutable, ordered collections that provide a convenient way to return multiple values from functions or to temporarily group related values without defining a custom type.

Creating tuples

Tuples in F# are created by enclosing comma-separated values in parentheses. While tuples of any size can be defined, they are most commonly used with a manageable number of elements to maintain readability and efficiency. F# provides built-in optimizations for tuples containing up to seven elements, often referred to as "tuple pairs" through "tuple septuples," but larger tuples can still be created and used without restrictions.

creating_tuples.fsx
// Pair (2-tuple)
let nameAge = ("John", 32)
printfn "Name and age: %A" nameAge

// Triple (3-tuple)
let rgbColor = (255, 128, 64)
printfn "RGB color: %A" rgbColor

// Mixed types
let personData = ("Alice", 28, 165.5, true)
printfn "Person data: %A" personData

// Single-element tuples do not exist in F#
// let single = ("hello",)

// Empty tuple (unit)
let empty = ()
printfn "Empty tuple: %A" empty

This example shows different ways to create tuples. Note that a single-element tuples do not exist in F# and the empty tuple () represents the unit type.

λ dotnet fsi creating_tuples.fsx
Name and age: ("John", 32)
RGB color: (255, 128, 64)
Person data: ("Alice", 28, 165.5, true)
Empty tuple: ()

Omitting parentheses

F# allows tuples to be defined without parentheses, making the syntax cleaner and more concise. However, this can lead to confusion when the syntax is ambiguous. For example, the expression 1, 2 + 3 is interpreted as a tuple (1, 5) rather than the intended (1, 2) + 3. To avoid ambiguity, it is recommended to use parentheses when creating tuples, especially in complex expressions.

omitting_parens.fsx
let nameAge = "John", 32
printfn "Name and age: %A" nameAge

let rgbColor = 255, 128, 64
printfn "RGB color: %A" rgbColor

let personData = "Alice", 28, 165.5, true
printfn "Person data: %A" personData

This example shows that parentheses can be omitted when creating tuples.

Accessing tuple elements

F# provides several ways to access tuple elements. You can use pattern matching, the fst and snd functions for pairs, or direct deconstruction.

accessing_elements.fsx
let person = ("Robert", "Novak", 45)

// Using fst and snd (for pairs only)
let pair = ("John", "Doe")
printfn "First: %s, Last: %s" (fst pair) (snd pair)

// Pattern matching
match person with
| (firstName, lastName, age) ->
    printfn "%s %s is %d years old" firstName lastName age

// Deconstructing directly
let (fName, lName, years) = person
printfn "Deconstructed: %s %s, age %d" fName lName years

// Accessing elements of larger tuples
let coords = (10.5, 20.3, 5.0, "home")
let (x, y, z, label) = coords
printfn "Coordinates: %s (%.1f, %.1f, %.1f)" label x y z

This code demonstrates various ways to access tuple elements. Pattern matching is the most flexible approach, while fst and snd are convenient for pairs. Deconstruction assigns elements directly to variables.

λ dotnet fsi accessing_elements.fsx
First: John, Last: Doe
Robert Novak is 45 years old
Deconstructed: Robert Novak, age 45
Coordinates: home (10.5, 20.3, 5.0)

Using tuples with functions

Tuples are commonly used to return multiple values from functions or to pass multiple arguments to functions in a structured way. They provide a simple alternative to out parameters or custom types in many cases.

functions_with_tuples.fsx
// Function returning a tuple
let divide x y =
    let quotient = x / y
    let remainder = x % y
    (quotient, remainder)

let result = divide 15 4
printfn "15 divided by 4: %A" result

// Function taking a tuple parameter
let printCoordinates (x, y, z) =
    printfn "Position: (%.1f, %.1f, %.1f)" x y z

let position = (3.5, 2.0, 1.5)
printCoordinates position

// Curried vs tuple arguments
let addCurried x y = x + y
let addTupled (x, y) = x + y

printfn "Curried add: %d" (addCurried 3 4)
printfn "Tupled add: %d" (addTupled (3, 4))

This example shows functions returning tuples and accepting tuple parameters. Note the difference between curried functions (multiple parameters) and functions taking a single tuple parameter. The tuple version requires all arguments to be supplied at once.

λ dotnet fsi functions_with_tuples.fsx
15 divided by 4: (3, 3)
Position: (3.5, 2.0, 1.5)
Curried add: 7
Tupled add: 7

Tuple pattern matching

Pattern matching with tuples allows for concise branching logic based on the structure and contents of tuples. This is a powerful feature for handling different cases in your code.

pattern_matching.fsx
let classifyPoint (x, y) =
    match (x, y) with
    | (0.0, 0.0) -> "Origin"
    | (_, 0.0) -> "On x-axis"
    | (0.0, _) -> "On y-axis"
    | (x, y) when x > 0.0 && y > 0.0 -> "Quadrant I"
    | (x, y) when x < 0.0 && y > 0.0 -> "Quadrant II"
    | (x, y) when x < 0.0 && y < 0.0 -> "Quadrant III"
    | _ -> "Quadrant IV"

printfn "%s" (classifyPoint (0.0, 0.0))
printfn "%s" (classifyPoint (2.5, 0.0))
printfn "%s" (classifyPoint (-1.0, 3.0))
printfn "%s" (classifyPoint (3.0, -4.0))

let describeSize (width, height) =
    match (width, height) with
    | (w, h) when w = h -> "Square"
    | (w, h) when w > h -> "Landscape"
    | _ -> "Portrait"

printfn "%s" (describeSize (100, 100))
printfn "%s" (describeSize (200, 100))
printfn "%s" (describeSize (80, 120))

The example demonstrates using tuple pattern matching to classify points in a 2D plane and to describe rectangle proportions. The underscore _ acts as a wildcard, matching any value.

λ dotnet fsi pattern_matching.fsx
Origin
On x-axis
Quadrant II
Quadrant IV
Square
Landscape
Portrait

Tuple type annotations

Tuple types can be explicitly annotated to specify the types of their elements. This is useful for documentation and can help catch type errors at compile time.

type_annotations.fsx
// Annotating tuple types
let person: string * int = ("Alice", 30)
let coords: float * float * string = (45.2, -122.6, "Portland")

// Function with annotated tuple parameter
let printPerson (p: string * int) =
    let (name, age) = p
    printfn "%s is %d years old" name age

printPerson person

// Type inference with tuples
let getDimensions () : int * int =
    let width = 800
    let height = 600
    (width, height)

let (screenWidth, screenHeight) = getDimensions()
printfn "Resolution: %dx%d" screenWidth screenHeight

This code shows how to add type annotations to tuples. The asterisk * separates the types of tuple elements. Annotations can be added to tuple variables, parameters, and return types.

λ dotnet fsi type_annotations.fsx
Alice is 30 years old
Resolution: 800x600

Comparing tuples

Tuples in F# support structural comparison when all their elements are comparable. Comparison is done element by element in order.

comparing_tuples.fsx
let point1 = (2, 3)
let point2 = (2, 4)
let point3 = (1, 5)

printfn "point1 = point2? %b" (point1 = point2)
printfn "point1 < point2? %b" (point1 < point2)
printfn "point1 > point3? %b" (point1 > point3)

// Sorting a list of tuples
let points = [(1, 5); (2, 3); (2, 4); (1, 4)]
let sorted = List.sort points
printfn "Sorted points: %A" sorted

// Comparing tuples with different types
let a = ("apple", 3)
let b = ("banana", 2)
printfn "'apple' vs 'banana': %b" (a < b)

The example demonstrates tuple comparison. Tuples are compared by their first elements, then second elements if the first are equal, and so on. This makes them naturally sortable when all elements are comparable.

λ dotnet fsi comparing_tuples.fsx
point1 = point2? false
point1 < point2? true
point1 > point3? true
Sorted points: [(1, 4); (1, 5); (2, 3); (2, 4)]
'apple' vs 'banana': true

F# tuples are versatile, lightweight data structures that excel at temporarily grouping related values and returning multiple values from functions. Their simple syntax, pattern matching support, and built-in comparison make them ideal for many programming scenarios. While they shouldn't replace proper data types for complex domain modeling, tuples are an essential tool in any F# programmer's toolkit.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.