Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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).

like image 260
Julien Pires Avatar asked Jan 31 '26 21:01

Julien Pires


2 Answers

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
like image 119
John Palmer Avatar answered Feb 02 '26 09:02

John Palmer


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
like image 28
Guy Coder Avatar answered Feb 02 '26 09:02

Guy Coder