简体   繁体   English

F# 中的列表理解与高阶函数

[英]List comprehension vs high-order functions in F#

I come from SML background and feel quite comfortable with high-order functions.我来自 SML 背景,对高阶函数感觉很舒服。 But I don't really get the idea of list comprehension.但我真的不明白列表理解的概念。 Is there any situation where list comprehension is more suitable than high-order functions on List and vice versa?是否存在列表理解比List上的高阶函数更合适的情况,反之亦然?

I heard somewhere that list comprehension is slower than high-order functions, should I avoid to use it when writing performance-critical functions?我在某处听说列表理解比高阶函数慢,我应该在编写性能关键函数时避免使用它吗?

For the example' sake, take a look at Projecting a list of lists efficiently in F# where @cfern's answer contains two versions using list comprehension and high-order functions respectively:为了这个例子,看看在F# 中有效地投影列表列表,其中@cfern 的答案分别包含使用列表理解和高阶函数的两个版本:

let rec cartesian = function
  | [] -> [[]]
  | L::Ls -> [for C in cartesian Ls do yield! [for x in L do yield x::C]]

and:和:

let rec cartesian2 = function
  | [] -> [[]]
  | L::Ls -> cartesian2 Ls |> List.collect (fun C -> L |> List.map (fun x->x::C))

Choosing between comprehensions and higher-order functions is mostly a matter of style.在推导式和高阶函数之间进行选择主要是风格问题。 I think that comprehensions are sometimes more readable, but that's just a personal preference.我认为理解有时更具可读性,但这只是个人喜好。 Note that the cartesian function could be written more elegantly like this:请注意, cartesian function 可以更优雅地写成这样:

let rec cartesian = function  
  | [] -> [[]]  
  | L::Ls -> 
     [ for C in cartesian Ls do for x in L do yield x::C ]

The interesting case is when writing recursive functions.有趣的情况是在编写递归函数时。 If you use sequences (and sequence comprehensions), they remove some unnecessary allocation of temporary lists and if you use yield!如果您使用序列(和序列推导),它们会删除一些不必要的临时列表分配,如果您使用yield! in a tail-call position, you can also avoid stack overflow exceptions:在尾调用 position 中,您还可以避免堆栈溢出异常:

let rec nums n = 
  if n = 100000 then []
  else n::(nums (n+1))
// throws StackOverflowException
nums 0 

let rec nums n = seq {
  if n < 100000 then
    yield n
    yield! nums (n+1) }
// works just fine
nums 0 |> List.ofSeq 

This is quite an interesting pattern, because it cannot be written in the same way using lists.这是一个非常有趣的模式,因为它不能以使用列表的相同方式编写。 When using lists, you cannot return some element and then make a recursive call, because it corresponds to n::(nums...) , which is not tail-recursive.使用列表时,您不能返回某个元素然后进行递归调用,因为它对应于n::(nums...) ,它不是尾递归的。

Looking at the generated code in ILSpy, you can see that list comprehensions are compiled to state machines (like methods using yield return in C#), then passed to something like List.ofSeq .查看 ILSpy 中生成的代码,您可以看到列表推导被编译到 state 机器(类似于在 C# 中使用yield return的方法),然后传递给类似List.ofSeq的东西。 Higher-order functions, on the other hand, are hand-coded, and frequently use mutable state or other imperative constructs to be as efficient as possible.另一方面,高阶函数是手动编码的,并且经常使用可变的 state 或其他命令式构造以尽可能高效。 As is often the case, the general-purpose mechanism is more expensive.通常情况下,通用机制更昂贵。

So, to answer your question, if performance is critical there is usually a higher-order function specific to your problem that should be preferred.因此,要回答您的问题,如果性能至关重要,通常应该首选针对您的问题的高阶 function。

Adding to Tomas Petricek's answer.添加到 Tomas Petricek 的答案。 You can make the list version tail recursive.您可以使列表版本尾递归。

let nums3 n =
    let rec nums3internal acc n = 
        if n = 100000 then
            acc
        else
            nums3internal (n::acc) (n+1) //Tail Call Optimization possible

    nums3internal [] n |> List.rev

nums3 0

With the added benefit of a considerable speedup.具有显着加速的额外好处。 At least when I measured with the stopwatch tool I get.至少当我用我得到的秒表工具测量时。 (nums2 being the algorithm using Seq). (nums2 是使用 Seq 的算法)。

Nums2 takes 81.225500ms
Nums3 takes 4.948700ms

For higher numbers this advantage shrinks, because List.rev is inefficient.对于较大的数字,此优势会缩小,因为 List.rev 效率低下。 Eg for 10000000 I get:例如,对于 10000000,我得到:

Nums2 takes 11054.023900ms
Nums3 takes 8256.693100ms

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM