Search code examples
f#iterablefunction-parameter

Passing a list as positional arguments to a function like Python in F#


In Python, you can write this:

def add(a, b, c):
    return a + b + c

list_of_args = [4, 5, 6]
print(add(*list_of_args))

The asterisk in front of list_of_args expands the iterable so that its elements are the values of the parameters a, b and c.

Can you do something similar in F#? Specifically, I'm looking for a good or idiomatic F# solution and do not want to muck around with reflection and so on.


Solution

  • F# does not have anything like this out of the box - mainly because F# is statically typed language and so supporting similar patters is difficult (list may only contain values of one type, while function may have different parameters).

    As mentioned in the linked answer, you can emulate similar idea using reflection, which is going to be slow and unsafe, but if you have really good reason for doing this, you might give it a try.

    Using the tupleToList function from the previous answer and a few active patterns, you can write:

    // Converts any F# tuple to a list of objects using reflection
    let tupleToList t = 
        if Microsoft.FSharp.Reflection.FSharpType.IsTuple(t.GetType()) 
            then Some (Microsoft.FSharp.Reflection.FSharpValue.GetTupleFields t |> Array.toList)
            else None
    
    // Active pattern that accepts any object and extracts its members
    // if it is a tuple or a sequence of values (e.g. list)
    let (|Arguments|_|) (a:obj) = 
      match a, tupleToList a with
      | _, Some t -> Some t
      | :? System.Collections.IEnumerable as l, _ -> 
          l |> Seq.cast |> List.ofSeq |> Some
      | _ -> None
    
    // Treat the argument as an int (this may fail)
    let (|Int|_|) (a:obj) = match a with :? int as n -> Some n | _ -> None
    
    // Function that assumes to get three integers
    let f (Arguments [Int a;Int b;Int c]) = 
      printfn "%d" (a + b + c)
    
    f (1, 2, 3) // Call with tuple
    f [1;2;3]   // Call with a list
    f (1, "hi", 3, 141.1)  // This will fail at runtime, but compiler allows it :-(
    

    This is probably not very idiomatic F# and I would try to avoid it, but it might do the trick.