F# nested List.iter calls

2019-08-15 13:52发布

问题:

I have an F# function and I want to try varying some of the parameters and testing all such combinations. Is this the right approach? (The parentheses get a bit dense...):

let MyFunc a b c x y z = 
  ...
  q


let UploadResult a b c x y z q =
  ...
  ()


let a = 5.0
let b = 0
let c = System.DateTime.Today
let xList = [-1.0; 0.0; 1.0]
let yList = [2; 4; 6; 8; 10]
let zList = [0.1; 0.001]

xList  |> List.iter (fun x ->
(yList |> List.iter (fun y ->
(zList |> List.iter (fun z ->
MyFunc a b c x y z 
|> UploadResult a b c x y z ))) ))
|> ignore

So I want to upload 3x5x2=30 results, and write it nicely. Thanks for any advice.

回答1:

In fact, your primary goal is creating a Cross-Product (or Cartesian Product) of several lists, and there are several options considered "good practice" among F# developers:

1. (removed for-comprehension as other answer already suggested this)

2. Use Computation Expressions (in the rest of Functional Programming world, it is often called Monad):

type Product () =
    member this.Bind (l,f) = List.collect f l    
    member this.Return n = [n]

let ret02 = Product() {
    let! x = xList
    let! y = yList
    let! z = zList
    MyFunc a b c x y z
    |> UploadResult a b c x y z
}

3. If you only worry about parentheses, use the high precedence, right associative backward pipe, (more info)

let inline (^<|) f a = f a

Then, your code would need a minimal modification (albeit still not very clean):

let ret03 =
    xList  |> List.iter ^<| fun x ->
    yList  |> List.iter ^<| fun y ->
    zList  |> List.iter ^<| fun z ->
        MyFunc a b c x y z
        |> UploadResult a b c x y z
        |> ignore


回答2:

For purely imperative operations like uploading results, I would not really worry too much. Using List.iter as in your code sample does the trick. I would probably prefer for loop, just because then it is obvious that this piece of code has side-effect that matters:

for x in xList do
  for y in yList do
    for z in zList do
      MyFunc a b c x y z |> UploadResult a b c x y z

If you wanted to do something clever, you could write a function that produces all combination of parameters from two lists:

let product xl yl = 
  seq { for x in xl do
          for y in yl do
            yield x, y }

The nice thing is that you can also use it multiple times: product xList (product yList zList). This gives you back a list of tuples that you can again iterate over:

for (x,y), z in product (product xList yList) zList do
    MyFunc a b c x y z 
    |> UploadResult a b c x y z

The not so nice thing about this is that you'll end up with nested tuples - which is why I'd probably go with just a plain loop. (Or if you always have exactly 3 lists, then the solution in the other answer, which is similar to product, but optimized for 3 lists)



回答3:

let MyFunc a b c x y z = 
    42

let UploadResult a b c x y z q =
    printfn "%A %A %A %A %A %A %A" a b c x y z q

let a = 5.0
let b = 0
let c = System.DateTime.Today
let xList = [-1.0; 0.0; 1.0]
let yList = [2; 4; 6; 8; 10]
let zList = [0.1; 0.001]

let perm xs ys zs =
    [for x in xs do
        for y in ys do 
            for z in zs do
                yield x,y,z]
let f (x,y,z) = MyFunc a b c  x y z |> UploadResult a b c  x y z
perm xList yList zList |> List.iter f