F# 查询表达式
最后修改日期:2025 年 5 月 17 日
在本文中,我们将深入探讨 F# 中的查询表达式——这是一项强大的功能,能够以声明式和直观的方式编写查询。
F# 查询表达式提供了一种优雅的方式来从各种数据源检索和转换数据,包括集合、数据库和结构化数据集。它们提供了一种类似 SQL 的语法,可以与 F# 的函数式编程功能无缝集成,使开发人员能够构建高效且富有表现力的查询。通过利用查询表达式,F# 可以在保持代码的可读性和可组合性的同时,实现简洁的数据操作。
基本查询表达式
最简单的查询表达式从源集合中选择数据。F# 提供了 `select`、`where` 和 `sortBy` 等多个运算符来过滤和转换数据。
open System
let vals = [| 1; 2; 3; 4; 5; 6|]
let lst = query {
for e in vals do
last
}
Console.WriteLine(lst)
let fst = query {
for e in vals do
head
}
Console.WriteLine(fst)
let n = query {
for e in vals do
nth 3
}
Console.WriteLine(n)
此示例演示了基本的查询操作。`last` 运算符获取最后一个元素,`head` 获取第一个元素,`nth` 获取特定索引处的元素。查询表达式包含在 `query { }` 块中。
λ dotnet fsi basic_query.fsx 6 1 4
使用 where 过滤
`where` 运算符根据条件过滤元素。它类似于 SQL 中的 WHERE 子句或函数式编程中的 filter 操作。
open System
let vals = [| 1; 2; 3; 4; 5; 6|]
let res = query {
for v in vals do
where (v <> 3)
select v
}
for e in res do
Console.WriteLine(e)
Console.WriteLine(vals.GetType())
此代码从数组中过滤掉值 3。`where` 子句包含条件,`select` 指定要返回的内容。结果是一个我们可以迭代的 IEnumerable。
λ dotnet fsi where_operator.fsx 1 2 4 5 6 Microsoft.FSharp.Core.FSharpOption`1[System.Int32[]]
计数和选择元素
查询表达式可以计算元素并对自定义类型执行更复杂的操作。`count` 运算符返回匹配查询的元素数量。
open System
type User = {
Name: string
Occupation: string
}
let users = [
{ Name = "John Doe"; Occupation = "gardener" }
{ Name = "Roger Roe"; Occupation = "driver" }
{ Name = "Thomas Monroe"; Occupation = "trader" }
{ Name = "Gregory Smith"; Occupation = "teacher" }
{ Name = "Lucia Bellington"; Occupation = "teacher" }
]
let n = query {
for user in users do
select user
count
}
Console.WriteLine(n)
let last = query {
for user in users do
last
}
Console.WriteLine(last)
Console.WriteLine("teachers:")
let teachers = query {
for user in users do
where (user.Occupation = "teacher")
select user
}
teachers |> Seq.iter Console.WriteLine
此示例处理 User 记录列表。我们计算所有用户,获取最后一个用户,并按职业过滤用户。`where` 子句过滤教师,`select` 返回匹配的用户。
λ dotnet fsi count_operator.fsx
5
{ Name = "Lucia Bellington"; Occupation = "teacher" }
teachers:
{ Name = "Gregory Smith"; Occupation = "teacher" }
{ Name = "Lucia Bellington"; Occupation = "teacher" }
数据排序
`sortBy` 和 `thenBy` 运算符允许按一个或多个字段对数据进行排序。`sortBy` 执行主要排序,而 `thenBy` 添加次要排序标准。
open System
type User = {
FirstName: string
LastName: string
Salary: int
}
let users = [
{ FirstName = "Robert"; LastName = "Novak"; Salary = 1770 }
{ FirstName = "John"; LastName = "Doe"; Salary = 1230 }
{ FirstName = "Lucy"; LastName = "Novak"; Salary = 670 }
{ FirstName = "Ben"; LastName = "Walter"; Salary = 2050 }
{ FirstName = "Robin"; LastName = "Brown"; Salary = 2300 }
{ FirstName = "Amy"; LastName = "Doe"; Salary = 1250 }
{ FirstName = "Joe"; LastName = "Draker"; Salary = 1190 }
{ FirstName = "Janet"; LastName = "Doe"; Salary = 980 }
{ FirstName = "Peter"; LastName = "Novak"; Salary = 990 }
{ FirstName = "Albert"; LastName = "Novak"; Salary = 1930 }
]
let sorted = query {
for user in users do
sortBy user.LastName
thenBy user.Salary
select user
}
sorted |> Seq.iter Console.WriteLine
此代码首先按姓氏对用户进行排序,然后按薪水排序。结果是一个用户序列,按姓氏字母顺序排列,姓氏相同的用户按薪水排序。
λ dotnet fsi sorting.fsx
{ FirstName = "Robin"; LastName = "Brown"; Salary = 2300 }
{ FirstName = "John"; LastName = "Doe"; Salary = 1230 }
{ FirstName = "Amy"; LastName = "Doe"; Salary = 1250 }
{ FirstName = "Janet"; LastName = "Doe"; Salary = 980 }
{ FirstName = "Joe"; LastName = "Draker"; Salary = 1190 }
{ FirstName = "Lucy"; LastName = "Novak"; Salary = 670 }
{ FirstName = "Peter"; LastName = "Novak"; Salary = 990 }
{ FirstName = "Robert"; LastName = "Novak"; Salary = 1770 }
{ FirstName = "Albert"; LastName = "Novak"; Salary = 1930 }
{ FirstName = "Ben"; LastName = "Walter"; Salary = 2050 }
分组和聚合
查询表达式支持数据分组和执行总和、平均值、计数等聚合操作。`groupBy` 运算符按键对元素进行分组,并且可以将聚合函数应用于每个组。
open System.Linq
type Revenue =
{ Id: int
Quarter: string
Amount: int }
let revenues = [
{ Id = 1; Quarter = "Q1"; Amount = 2340 };
{ Id = 2; Quarter = "Q1"; Amount = 1200 };
{ Id = 3; Quarter = "Q1"; Amount = 980 };
{ Id = 4; Quarter = "Q2"; Amount = 340 };
{ Id = 5; Quarter = "Q2"; Amount = 780 };
{ Id = 6; Quarter = "Q3"; Amount = 2010 };
{ Id = 7; Quarter = "Q3"; Amount = 3370 };
{ Id = 8; Quarter = "Q4"; Amount = 540 }
]
query {
for revenue in revenues do
groupBy revenue.Quarter into g
where (g.Count() = 2)
select {| Quarter = g.Key
Total = g.Sum(fun c -> c.Amount) |}
}
|> Seq.iter (fun e -> printfn "%A" e)
此示例按季度对收入进行分组,过滤掉只有 2 个条目的季度,并计算每个符合条件的季度的总金额。结果是一个具有季度和总金额的匿名记录。
λ dotnet fsi grouping.fsx
{ Quarter = "Q1"; Total = 4520 }
{ Quarter = "Q2"; Total = 1120 }
{ Quarter = "Q3"; Total = 5380 }
F# 查询表达式为处理数据提供了一种强大、声明式的方法。它们提供类似 SQL 的语法来过滤、排序、分组和转换数据,同时保留 F# 的类型安全和函数式编程优势。无论是在内存中的集合还是外部数据源,查询表达式都可以使您的数据处理代码更具可读性和可维护性。