ZetCode

F# 记录

最后修改时间 2025 年 5 月 1 日

在本文中,我们将探讨如何在 F# 中有效使用记录并理解它们在数据结构中的作用。

一个 记录 是将命名的值组合在一个结构化格式中。默认情况下,记录是不可变的,这意味着它们的值在初始化后不能被更改。然而,它们可以包含成员,如函数或计算属性,从而增强其功能。如果需要可变性,可以使用 mutable 关键字将字段显式标记为可变

F# 记录简单示例

记录使用 type 关键字定义。值在 { } 括号之间指定。

simple.fsx
type User =
    { FirstName: string; LastName: string; Occupation: string; Salary: int }

let users =
    [ { FirstName = "Robert"; LastName = "Novak"; Occupation = "teacher"; Salary = 1770 }
      { FirstName = "John"; LastName = "Doe"; Occupation = "gardener"; Salary = 1230 }
      { FirstName = "Lucy"; LastName = "Novak"; Occupation = "accountant"; Salary = 670 } ]

users |> List.iter (printfn "%A")

该程序定义了一个 User 记录。我们从记录类型创建了一个包含三个用户的列表。然后迭代该列表。

type User =
  { FirstName: string; LastName: string; Occupation: string; Salary: int }

记录类型定义了三个字段。字段之间用分号分隔。这些分号是可选的。字段名与其类型之间用冒号分隔。

let users =
    [ { FirstName = "Robert"; LastName = "Novak"; Occupation = "teacher"; Salary = 1770 }
      { FirstName = "John"; LastName = "Doe"; Occupation = "gardener"; Salary = 1230 }
      { FirstName = "Lucy"; LastName = "Novak"; Occupation = "accountant"; Salary = 670 } ]

我们有一个包含三个用户的列表。字段名与值之间用等号分隔。

λ dotnet fsi simple.fsx
{ FirstName = "Robert"
  LastName = "Novak"
  Occupation = "teacher"
  Salary = 1770 }
{ FirstName = "John"
  LastName = "Doe"
  Occupation = "gardener"
  Salary = 1230 }
{ FirstName = "Lucy"
  LastName = "Novak"
  Occupation = "accountant"
  Salary = 670 }

当我们将每个字段放在单独的一行时,可以省略分号。

simple2.fsx
type User =
    { FirstName: string
      LastName: string
      Occupation: string
      Salary: int }

let users =
    [ { FirstName = "Robert"
        LastName = "Novak"
        Occupation = "teacher"
        Salary = 1770 }
      { FirstName = "John"
        LastName = "Doe"
        Occupation = "gardener"
        Salary = 1230 }
      { FirstName = "Lucy"
        LastName = "Novak"
        Occupation = "accountant"
        Salary = 670 } ]

users |> List.iter (printfn "%A")

该程序在没有分号的情况下定义和创建记录。

F# 记录访问字段

记录的字段通过点字符进行访问。

access.fsx
type User = { Name: string; Occupation: string }

let u =
    { Name = "John Doe"
      Occupation = "gardener" }

printfn "%s" u.Name
printfn "%s" u.Occupation

我们创建了一个带有两个字段的用户记录,然后打印出这些字段的值。字段名通过点字符进行访问。

λ dotnet fsi access.fsx
John Doe
gardener

F# 记录字段顺序

F# 通过字段的名称和类型来确定记录的类型,而不是字段使用的顺序。

order.fsx
type User = { Name: string; Occupation: string }

let u1 =
    { Name = "John Doe"
      Occupation = "gardener" }

let u2 =
    { Occupation = "driver"
      Name = "Roger Roe" }

printfn "%A" u1
printfn "%A" u2

我们定义了两个记录对象。NameOccupation 字段的定义顺序无关紧要。

λ dotnet fsi order.fsx
{ Name = "John Doe"
  Occupation = "gardener" }
{ Name = "Roger Roe"
  Occupation = "driver" }

F# 克隆记录

可以使用 with 从现有记录派生新记录。

clone.fsx
type User = { Name: string; Occupation: string }

let u1 =
    { Name = "John Doe"
      Occupation = "gardener" }

printfn "%A" u1

let u2 = { u1 with Name = "Peter Smith"}
printfn "%A" u2

在此示例中,我们基于现有用户克隆了一个新用户。

let u2 = { u1 with Name = "Peter Smith"}

我们从 user1 派生 user2;我们保留职业并更改姓名。

λ dotnet fsi clone.fsx
{ Name = "John Doe"
  Occupation = "gardener" }
{ Name = "Peter Smith"
  Occupation = "gardener" }

F# 记录输出

%A 说明符用于漂亮地打印元组、记录和联合类型。%O 用于其他对象,使用 ToString。

output.fsx
type User =
  { Name: string
    Occupation: string }
  override this.ToString() =
      sprintf "%s %s" this.Name this.Occupation

let u1 =
  { Name = "John Doe"
    Occupation = "gardener" }

let u2 =
  { Name = "Roger Roe"
    Occupation = "driver" }

printfn "%A" u1
printfn "%O" u2

我们定义了一个记录类型,并重写了 ToString 方法。我们使用 %A%O 说明符输出了记录。

λ dotnet fsi output.fsx
{ Name = "John Doe"
  Occupation = "gardener" }
Roger Roe driver

F# 记录解构

解构是将类型解包成单个部分。

decons.fsx
type User = { Name: string; Occupation: string }

let u1 =
    { Name = "John Doe"
      Occupation = "gardener" }

let { Name = n1; Occupation = o1 } = u1
printfn "%s %s" n1 o1

let { Name = _; Occupation = o2 } = u1
printfn "%s" o2

let { Name = n2 } = u1
printfn "%s" n2

该程序解构了一个用户记录。字段可以被省略。

λ dotnet fsi decons.fsx
John Doe gardener
gardener
John Doe

F# 嵌套记录

我们可以使用 and 将一个记录嵌套在另一个记录中。

nest.fsx
type User =
    { Name: string
      Occupation: string
      Address: Address }

and Address = { Line1: string; Line2: string }


let u1 =
    { Name = "John Doe"
      Occupation = "gardener"
      Address =
        { Line1 = "Address 1"
          Line2 = "Address 2" } }

printfn "%A" u1

let u2 =
    { Name = "Roger Doe"
      Occupation = "driver"
      Address =
        { Line1 = "Address 1"
          Line2 = "Address 2" } }

printfn "%A" u2

我们有一个 User 记录,其中嵌套了一个 Address 类型。

λ dotnet fsi nest.fsx
{ Name = "John Doe"
  Occupation = "gardener"
  Address = { Line1 = "Address 1"
              Line2 = "Address 2" }
  Colours = { Col1 = "red"
              Col2 = "blue" } }
{ Name = "Roger Doe"
  Occupation = "driver"
  Address = { Line1 = "Address 1"
              Line2 = "Address 2" }
  Colours = { Col1 = "red"
              Col2 = "green" } }

F# 记录相等性

记录具有结构相等性。结构相等性是指两个对象包含相同的值。

equality.fsx
type User =
    { Name: string
      Occupation: string }

let u1 =
    { Name = "John Doe"
      Occupation = "gardener" }

let u2 =
    { Name = "Roger Roe"
      Occupation = "driver" }

printfn "%A" (u1 = u2)

在此示例中,我们比较了两个用户记录。

F# 记录成员

记录中的成员可以使用 member 定义。

member.fsx
type User =
  { Name: string
    Occupation: string }

    member this.Info() =
        $"{this.Name} is a {this.Occupation}"


let u1 = { Name= "John Doe"; Occupation="gardener" }
let u2 = { Name= "Roger Roe"; Occupation="driver" }

printfn "%s" (u1.Info())
printfn "%s" (u2.Info())

在此示例中,我们定义了 Info 成员。

λ dotnet fsi member.fsx
John Doe is a gardener
Roger Roe is a driver

F# 记录模式匹配

记录可以与模式匹配一起使用。

pattern_match.fsx
type User =
    { FirstName: string
      LastName: string
      Occupation: string }

let users =
    [ { FirstName = "John"
        LastName = "Doe"
        Occupation = "gardener" }
      { FirstName = "Jane"
        LastName = "Doe"
        Occupation = "teacher" }
      { FirstName = "Roger"
        LastName = "Roe"
        Occupation = "driver" } ]

for user in users do
    match user with
    | { LastName = "Doe" } -> printfn "%A" user
    | _ -> ()

该示例打印了所有姓 Doe 的人。

| { LastName = "Doe" } -> printfn "%A" user

在此分支中,我们检查所有 LastName 等于 "Doe" 的记录。

在本文中,我们使用 F# 中的记录类型进行了实践。

作者

我的名字是 Jan Bodnar,我是一名热情的程序员,拥有丰富的编程经验。我从 2007 年开始撰写编程文章。迄今为止,我已撰写了 1,400 多篇文章和 8 本电子书。我在编程教学方面拥有十多年的经验。