Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

List comprehension vs high-order functions in F#

I come from SML background and feel quite comfortable with high-order functions. 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?

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:

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))
like image 926
pad Avatar asked Jun 07 '11 09:06

pad


1 Answers

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:

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! in a tail-call position, you can also avoid stack overflow exceptions:

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.

like image 65
Tomas Petricek Avatar answered Oct 11 '22 13:10

Tomas Petricek