Reputation: 512
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
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
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
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
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