Julien Pires
Julien Pires

Reputation: 512

Append multiple List with computation expression

I have a function that generates some elements and returns them in a List. The function take a parameter to filter returned elements. The function looks like this:

let create x = [1..1000] |> List.filter (fun c -> (c % x) = 0)

Now I would like to call this function with multiple criteria and append all results into a single List.

At first I was thinking doing this:

let result = (create 100) 
            |> List.append (create 300) 
            |> List.append (create 500) 
            |> List.append (create 3)
printf "%A" result

But I didn't find this solution elegant and repetitive with the List.append expression.

I had tried a second solution:

let result = [100; 300; 500; 3] |> List.map (fun c -> create c) 
                                |> List.reduce (fun acc c -> acc |> List.append c)
printf "%A" result

This solution is more concise but there is something that I don't like about it. I found it less clear and more difficult to guess what we tried to achieve.

So, I was looking for a third solution and tried to implement a new solution with computation expression:

type AppendBuilder() =
    member this.Bind (x, f) = f x |> List.append x
    member this.Return x = x

And then use it like that:

let append = new AppendBuilder()
let result = append {
    let! a = create 100
    let! b = create 300
    let! c = create 500
    let! d = create 3

    return []
}
printf "%A" result

This last solution works and I prefer it. I found it more clear, easier to understand what we are doing and without the repetetive List.append expression. But there is still something wrong. I don't like the return [] part, this seems un-natural and unclear. But this trick is necessary to make it work.

The third solution is my favorite but I'm not sure this is the right way to solve my problem. It solves the problem, I get the correct result but I'm not sure if this is the right approach.

Can I keep the third solution without causing confusion about my intention? Does the solution is clear enough to keep it as is? I'm looking for a solution that is more focused on the action (here calling create x) without repetitive expression that parasite the code (here calling List.append).

Upvotes: 2

Views: 391

Answers (4)

John Palmer
John Palmer

Reputation: 25516

Since this was requested as an answer:

The easiest way is to use List.collect which esentially does the map and combining the lists in one step.

The solution in this case looks like

[100;300;500;3] |> List.collect create

Upvotes: 7

Guy Coder
Guy Coder

Reputation: 24976

let result = 
    [
        yield! [ 100 .. 100 .. 1001]
        yield! [ 300 .. 300 .. 1001]
        yield! [ 500 .. 500 .. 1001]
        yield! [ 3 .. 3 .. 1001]
    ]

Of note: Having worked with Python list I do find them more friendly because you can use the value 1000 instead of the value 1001 for F#.

If your create function is always a step function then using the built-in ability of List makes more sense. If you plan to use a create function that cannot make use of the built-in capabilities of List then the comment by John Palmer makes more sense.

let result = 
    [100;300;500;3] 
    |> List.collect create

A variation suggest in the comment by Anton Schwaighofer would be to allow the values for the create function to come in as a parameter.

let makeList parms =
    parms
    |> List.collect create

and then be used as

let result = makeList [100;300;500;3]

or even pointfree

let makeList = List.collect create

Upvotes: 5

krontogiannis
krontogiannis

Reputation: 1939

If the 3rd one is your favourite version then you might want to use list comprehension:

let result = 
    [ yield! create 100
      yield! create 300
      yield! create 500
      yield! create 3 ]

Upvotes: 2

Bartek Kobyłecki
Bartek Kobyłecki

Reputation: 2395

You can benefit from laziness of seq computation:

seq {
    yield! create 100
    yield! create 300
    yield! create 500
    yield! create 3
} |> List.ofSeq

and then compute the list using List.ofSeq

Upvotes: 1

Related Questions