0

the question in short: What is the most idiomatic way to do "recursive List comprehension" in F#?

more detailed: As I have learned so far (I am new to F#) we have essentially the following tools to "build up" lists: List.map and list comprehension. Imho they both do more or less the same thing, they generate a list by "altering" the elements of a given list (in case of comprehension the given list is of the form [k..n]).

What I want to do is to inductively build up lists (before people ask: for no other reason than curiosity) i.e. is there any built in function with the behavior one would expect from a function called something like "List.maplist" that might take as arguments

a function f : 'a List -> 'a and an n : int,

returning the list

[... ; f (f []) ; f [] ] of length n.

To illustrate what I mean I wrote such a function on my own (as an exercise)

let rec recListComprehension f n =
    if n=0 then []
    else
        let oldList = recListComprehension f (n-1)
        f (oldList) :: oldList 

or a bit less readable but in turn tail recursive:

let rec tailListComprehension f n list = 
    if n=0 then list
    else tailListComprehension f (n-1) ((f list)::list)

let trecListComprehension f n = tailListComprehension f n []

for example, a list containing the first 200 fibonacci numbers can be generated by

let fiboGen =
    function
    | a::b::tail -> a+b
    | _ -> 1UL

trecListComprehension (fiboGen) 200

to sum up the question: Is there a build in function in F# that behaves more or less like "trecListComprehension" and if not what is the most idiomatic way to achieve this sort of functionality?

PS: sorry for being a bit verbose..

D.F.F
  • 914
  • 1
  • 6
  • 17

3 Answers3

3

What is the most idiomatic way to do "recursive List comprehension" in F#?

It's the matter of style. You will encounter high-order functions more often. For certain situations e.g. expressing nested computation or achieving laziness, using sequence expression seems more natural.

To illustrate, your example is written in sequence expression:

let rec recListComprehension f n = seq { 
    if n > 0 then
        let oldList = recListComprehension f (n-1)
        yield f oldList
        yield! oldList }

recListComprehension fiboGen 200 |> Seq.toList

You have a very readable function with both laziness and tail-recursiveness which you can't easily achieve by using Seq.unfold.

Similarly, nested computation of cartesian product is more readable to use sequence expression / list comprehension:

let cartesian xs ys = 
    [ for x in xs do
        for y in ys do
          yield (x, y) ]

than to use high-order functions:

let cartesian xs ys = 
    List.collect (fun x -> List.map (fun y -> (x, y)) ys) xs

I once asked about differences between list comprehension and high-order functions which might be of your interest.

Community
  • 1
  • 1
pad
  • 41,040
  • 7
  • 92
  • 166
  • I'm not sure what "achieving laziness using sequence expression seems more natural" means. Isn't it best to use laziness when you want laziness and to use a plain higher-order function when you don't require laziness? – N_A May 07 '13 at 21:36
  • Well, I adjust the wording to be clear. When you don't need laziness, just use `[ ... ]` instead of `seq { ... }`. Some plain high-order functions are lazy as well e.g. `Seq.unfold` in your answer. – pad May 07 '13 at 21:57
  • Fair enough, I guess I meant more along the lines of rolling your own vs using a library implementation of something when it exists. `Seq.unfold` does exactly what he was looking for, no need to over-engineer it for unstated possibilities. – N_A May 07 '13 at 22:04
2

You're basically folding over the numeric range. So it could be written:

let listComp f n = List.fold (fun xs _ -> f xs :: xs) [] [1 .. n]

This has the added benefit of gracefully handling negative values of n.

Daniel
  • 47,404
  • 11
  • 101
  • 179
0

You could do a Seq.unfold and then do Seq.toList.

See the example from here:

let seq1 = Seq.unfold (fun state -> if (state > 20) then None else Some(state, state + 1)) 0

printfn "The sequence seq1 contains numbers from 0 to 20." 
for x in seq1 do printf "%d " x

let fib = Seq.unfold (fun state ->
    if (snd state > 1000) then None
    else Some(fst state + snd state, (snd state, fst state + snd state))) (1,1)

printfn "\nThe sequence fib contains Fibonacci numbers." 
for x in fib do printf "%d " x
N_A
  • 19,799
  • 4
  • 52
  • 98