F# 记录
最后修改时间 2025 年 5 月 1 日
在本文中,我们将探讨如何在 F# 中有效使用记录并理解它们在数据结构中的作用。
一个 记录 是将命名的值组合在一个结构化格式中。默认情况下,记录是不可变的,这意味着它们的值在初始化后不能被更改。然而,它们可以包含成员,如函数或计算属性,从而增强其功能。如果需要可变性,可以使用 mutable 关键字将字段显式标记为可变。
F# 记录简单示例
记录使用 type 关键字定义。值在 { } 括号之间指定。
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 }
当我们将每个字段放在单独的一行时,可以省略分号。
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# 记录访问字段
记录的字段通过点字符进行访问。
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# 通过字段的名称和类型来确定记录的类型,而不是字段使用的顺序。
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
我们定义了两个记录对象。Name 和 Occupation 字段的定义顺序无关紧要。
λ dotnet fsi order.fsx
{ Name = "John Doe"
Occupation = "gardener" }
{ Name = "Roger Roe"
Occupation = "driver" }
F# 克隆记录
可以使用 with 从现有记录派生新记录。
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。
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# 记录解构
解构是将类型解包成单个部分。
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 将一个记录嵌套在另一个记录中。
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# 记录相等性
记录具有结构相等性。结构相等性是指两个对象包含相同的值。
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 定义。
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# 记录模式匹配
记录可以与模式匹配一起使用。
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# 中的记录类型进行了实践。