ZetCode

F# 元组

最后修改日期:2025 年 5 月 17 日

F# 中的元组是轻量级数据结构,可将多个值(可能具有不同类型)组合在一起。它们是不可变的、有序的集合,提供了一种从函数返回多个值的便捷方式,或者在不定义自定义类型的情况下临时组合相关值。

创建元组

F# 中的元组是通过将逗号分隔的值括在括号中来创建的。虽然可以定义任何大小的元组,但它们最常用于数量适中的元素,以保持可读性和效率。F# 为包含最多七个元素的元组提供了内置优化,通常称为“元组对”到“元组七重奏”,但仍可以创建和使用更大的元组,没有任何限制。

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

此示例展示了创建元组的不同方法。请注意,F# 中不存在单元素元组,而空元组 () 代表 unit 类型。

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

省略括号

F# 允许在不使用括号的情况下定义元组,从而使语法更简洁、更精炼。但是,当语法不明确时,这可能导致混淆。例如,表达式 1, 2 + 3 被解释为元组 (1, 5),而不是预期的 (1, 2) + 3。为避免歧义,建议在创建元组时使用括号,尤其是在复杂表达式中。

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

此示例显示了在创建元组时可以省略括号。

访问元组元素

F# 提供了几种访问元组元素的方法。您可以使用模式匹配、用于对的 fstsnd 函数,或直接解构。

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

此代码演示了访问元组元素的各种方法。模式匹配是最灵活的方法,而 fstsnd 对于对很方便。解构直接将元素分配给变量。

λ 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)

将元组与函数一起使用

元组通常用于从函数返回多个值,或以结构化的方式将多个参数传递给函数。在许多情况下,它们提供了 out 参数或自定义类型的简单替代方案。

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))

此示例展示了返回元组和接受元组参数的函数。请注意柯里化函数(多个参数)和接受单个元组参数的函数之间的区别。元组版本要求一次性提供所有参数。

λ 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

元组模式匹配

使用元组进行模式匹配可以根据元组的结构和内容进行简洁的分支逻辑。这是处理代码中不同情况的强大功能。

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))

该示例演示了使用元组模式匹配对二维平面中的点进行分类以及描述矩形比例。下划线 _ 用作通配符,匹配任何值。

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

元组类型注解

可以显式注解元组类型以指定其元素的类型。这对于文档很有用,并且可以帮助在编译时捕获类型错误。

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

此代码显示了如何向元组添加类型注解。星号 * 用于分隔元组元素的类型。可以在元组变量、参数和返回类型中添加注解。

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

比较元组

当所有元素都可比较时,F# 中的元组支持结构化比较。比较是按顺序逐个元素进行的。

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)

该示例演示了元组比较。元组首先按第一个元素比较,如果第一个元素相等则按第二个元素比较,依此类推。这使得它们在所有元素都可比较时可以自然地排序。

λ 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# 元组是多功能、轻量级的数据结构,在临时组合相关值和从函数返回多个值方面表现出色。它们简单的语法、模式匹配支持和内置比较使其成为许多编程场景的理想选择。虽然它们不应取代复杂的域建模的适当数据类型,但元组是任何 F# 程序员工具箱中的重要工具。

作者

我的名字是 Jan Bodnar,我是一名充满激情的程序员,拥有丰富的编程经验。自 2007 年以来,我一直在撰写编程文章。迄今为止,我已撰写了 1400 多篇文章和 8 本电子书。我在编程教学方面拥有超过十年的经验。