LINQ 表达式是在 F# 中操作数据的可接受方式吗?
Are LINQ expressions an acceptable way to manipulate data in F#?
我是 F# 程序员的初学者。我知道 F# 是函数式的,并且更喜欢数据通过函数传输的样式,例如集合上的 map
和 iter
函数。尽管如此,LINQ 表达式还是提供了一种替代的、可读性强的方法来操作集合;但是,我不确定它是否更有必要并且破坏了使用函数式语言的意义。
例如,没有 LINQ:
let listOfPrimes n =
[1UL..n]
|> List.choose (fun i -> match i with
| i when isPrime i -> Some i
| _ -> None)
使用 LINQ,我们可以:
let listOfPrimes n =
query {
for i in [1UL..n] do
where (isPrime i)
select i
}
|> List.ofSeq
我注意到在使用 LINQ 时我们需要将结果序列转换为列表。那么,实际性能差异是什么? LINQ 在实际数据库查询之外是否在风格上不受欢迎?什么时候使用该场景之外的查询来操作集合数据是合适的?
我认为这是一个偏好问题 - 有些人更喜欢使用高阶函数编写代码,有些人更喜欢 LINQ 风格的 query
表达式。
值得注意的是,还有序列表达式,可以看作是query
语法的简单版本。序列表达式不会让您轻松访问其他查询运算符,但它们可以很好地处理简单的事情,您还可以使用 [ ... ]
表示法将结果作为列表获取:
let listOfPrimes n =
[ for i in [1UL..n] do
if (isPrime i) then yield i ]
我个人的喜好是:
- 使用序列表达式进行简单的过滤&投影&选择
- 对其他操作使用高阶函数(可能除了查询表达式更好的复杂分组和连接之外)。
- 使用查询表达式访问数据库
除了与期望 IQueryables
的 C# API 进行互操作之外,单独看到 query
肯定会引起一些注意。但这主要是因为已经提到的 "native" 集合理解,如果您想使用类似的语法,您可以在 F# 中使用它。
至于不同选择的比较如何,我用下面的代码做了一点测试:
module TestHof =
let make n =
seq { 1 .. n }
|> Seq.map (fun x -> x * x)
|> Seq.filter (fun x -> x > n/2)
|> Seq.toList
module TestExpr =
let make n =
[ for i in 1 .. n do
let x = i * i
if x > n/2 then yield x ]
module TestSeqExpr =
let make n =
seq { for i in 1 .. n do
let x = i * i
if x > n/2 then yield x }
|> Seq.toList
module TestQuery =
let make n =
query { for i in 1 .. n do
select (i * i) into x
where (x > n/2) }
|> Seq.toList
运行它们在 FSI 中的时间如下:
> TestHof.make 1000000;;
Real: 00:00:00.796, CPU: 00:00:00.781, GC gen0: 3, gen1: 2, gen2: 0
> TestExpr.make 1000000;;
Real: 00:00:00.613, CPU: 00:00:00.625, GC gen0: 3, gen1: 2, gen2: 0
> TestSeqExpr.make 1000000;;
Real: 00:00:00.563, CPU: 00:00:00.562, GC gen0: 3, gen1: 2, gen2: 0
> TestQuery.make 1000000;;
Real: 00:00:05.638, CPU: 00:00:05.562, GC gen0: 20, gen1: 3, gen2: 0
因此 query
明显落后于其他选项。
此处的一个有趣观察是,用于列表推导 (TestExpr
) 的 IL 代码和稍后转换为列表的序列表达式 (TestSeqExpr
) 完全相同。这意味着列表理解本质上是一个序列表达式,包裹在对 Seq.toList
的调用中——这很有意义,但也不是显而易见的事情。
我是 F# 程序员的初学者。我知道 F# 是函数式的,并且更喜欢数据通过函数传输的样式,例如集合上的 map
和 iter
函数。尽管如此,LINQ 表达式还是提供了一种替代的、可读性强的方法来操作集合;但是,我不确定它是否更有必要并且破坏了使用函数式语言的意义。
例如,没有 LINQ:
let listOfPrimes n =
[1UL..n]
|> List.choose (fun i -> match i with
| i when isPrime i -> Some i
| _ -> None)
使用 LINQ,我们可以:
let listOfPrimes n =
query {
for i in [1UL..n] do
where (isPrime i)
select i
}
|> List.ofSeq
我注意到在使用 LINQ 时我们需要将结果序列转换为列表。那么,实际性能差异是什么? LINQ 在实际数据库查询之外是否在风格上不受欢迎?什么时候使用该场景之外的查询来操作集合数据是合适的?
我认为这是一个偏好问题 - 有些人更喜欢使用高阶函数编写代码,有些人更喜欢 LINQ 风格的 query
表达式。
值得注意的是,还有序列表达式,可以看作是query
语法的简单版本。序列表达式不会让您轻松访问其他查询运算符,但它们可以很好地处理简单的事情,您还可以使用 [ ... ]
表示法将结果作为列表获取:
let listOfPrimes n =
[ for i in [1UL..n] do
if (isPrime i) then yield i ]
我个人的喜好是:
- 使用序列表达式进行简单的过滤&投影&选择
- 对其他操作使用高阶函数(可能除了查询表达式更好的复杂分组和连接之外)。
- 使用查询表达式访问数据库
除了与期望 IQueryables
的 C# API 进行互操作之外,单独看到 query
肯定会引起一些注意。但这主要是因为已经提到的 "native" 集合理解,如果您想使用类似的语法,您可以在 F# 中使用它。
至于不同选择的比较如何,我用下面的代码做了一点测试:
module TestHof =
let make n =
seq { 1 .. n }
|> Seq.map (fun x -> x * x)
|> Seq.filter (fun x -> x > n/2)
|> Seq.toList
module TestExpr =
let make n =
[ for i in 1 .. n do
let x = i * i
if x > n/2 then yield x ]
module TestSeqExpr =
let make n =
seq { for i in 1 .. n do
let x = i * i
if x > n/2 then yield x }
|> Seq.toList
module TestQuery =
let make n =
query { for i in 1 .. n do
select (i * i) into x
where (x > n/2) }
|> Seq.toList
运行它们在 FSI 中的时间如下:
> TestHof.make 1000000;;
Real: 00:00:00.796, CPU: 00:00:00.781, GC gen0: 3, gen1: 2, gen2: 0
> TestExpr.make 1000000;;
Real: 00:00:00.613, CPU: 00:00:00.625, GC gen0: 3, gen1: 2, gen2: 0
> TestSeqExpr.make 1000000;;
Real: 00:00:00.563, CPU: 00:00:00.562, GC gen0: 3, gen1: 2, gen2: 0
> TestQuery.make 1000000;;
Real: 00:00:05.638, CPU: 00:00:05.562, GC gen0: 20, gen1: 3, gen2: 0
因此 query
明显落后于其他选项。
此处的一个有趣观察是,用于列表推导 (TestExpr
) 的 IL 代码和稍后转换为列表的序列表达式 (TestSeqExpr
) 完全相同。这意味着列表理解本质上是一个序列表达式,包裹在对 Seq.toList
的调用中——这很有意义,但也不是显而易见的事情。