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# 的类型安全和函数式编程优势。无论是在内存中的集合还是外部数据源,查询表达式都可以使您的数据处理代码更具可读性和可维护性。