我来自SML背景,对高阶函数感觉很舒服。但我真的不明白列表理解。 是否存在列表理解比List
上的高阶函数更合适的情况,反之亦然?
我听说列表理解比高阶函数慢,我应该避免在编写性能关键函数时使用它吗?
为了示例,请查看Projecting a list of lists efficiently in F#其中@ cfern的答案分别包含使用列表推导和高阶函数的两个版本:
let rec cartesian = function
| [] -> [[]]
| L::Ls -> [for C in cartesian Ls do yield! [for x in L do yield x::C]]
和
let rec cartesian2 = function
| [] -> [[]]
| L::Ls -> cartesian2 Ls |> List.collect (fun C -> L |> List.map (fun x->x::C))
答案 0 :(得分:11)
在理解和高阶函数之间进行选择主要是风格问题。我认为理解有时更具可读性,但这只是个人偏好。请注意,cartesian
函数可以更优雅地编写如下:
let rec cartesian = function
| [] -> [[]]
| L::Ls ->
[ for C in cartesian Ls do for x in L do yield x::C ]
有趣的情况是编写递归函数。如果使用序列(和序列推导),它们会删除一些不必要的临时列表分配,如果在尾部调用位置使用yield!
,也可以避免堆栈溢出异常:
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
这是一个非常有趣的模式,因为它不能使用列表以相同的方式编写。使用列表时,您不能返回某个元素然后进行递归调用,因为它对应于n::(nums ...)
,这不是尾递归。
答案 1 :(得分:4)
查看ILSpy中生成的代码,您可以看到列表推导被编译到状态机(比如在C#中使用yield return
的方法),然后传递给List.ofSeq
之类的东西。另一方面,高阶函数是手工编码的,并且经常使用可变状态或其他命令性构造尽可能高效。通常情况下,通用机制更加昂贵。
因此,要回答您的问题,如果性能至关重要,通常会有一个特定于问题的高阶函数,应该是首选。
答案 2 :(得分:0)
添加Tomas Petricek的回答。您可以使列表版本尾递归。
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
具有相当大的加速优势。至少当我用秒表工具测量时,我得到了。 (nums2是使用Seq的算法)。
Nums2 takes 81.225500ms
Nums3 takes 4.948700ms
对于更高的数字,这种优势会缩小,因为List.rev效率低下。例如。对于10000000,我得到:
Nums2 takes 11054.023900ms
Nums3 takes 8256.693100ms