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).
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
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With