Pass function as a parameter and overload it

249 views Asked by At

I want to time my functions, some of them use up to three parameters. Right now I'm using the same code below with some variations for the three.

let GetTime f (args : string) = 
    let sw = Stopwatch.StartNew()
    f (args)
    printfn "%s : %A" sw.Elapsed 

I want to replace the three functions with this one.

let GetTime f ( args : 'T[]) =
    let sW = Stopwatch.StartNew()
    match args.Length with
    | 1 -> f args.[0]
    | 2 -> f (args.[0] args.[1])
    printfn "%A" sW.Elapsed
    ()

But I'm getting an error of type mismatch, if I use the three functions it works. Is it possible to send the function as a parameter and use it like this?

4

There are 4 answers

0
cadull On BEST ANSWER

It isn't possible for the compiler to know how many arguments will be passed at runtime, so the function f must satisfy both 'T -> unit and 'T -> 'T -> unit. This form also requires all arguments to be of the same type.

The following approach delays the function execution and may be suitable for your needs.

let printTime f =
    let sw = Stopwatch.StartNew()
    f() |> ignore
    printfn "%A" sw.Elapsed

let f1 s = String.length s
let f2 s c = String.concat c s

printTime (fun () -> f1 "Test")
printTime (fun () -> f2 [| "Test1"; "Test2" |] ",")
0
Marc Sigrist On

You're probably thinking of passing a method group as an argument to GetTime, and then having the compiler decide which overload of the method group to call. That's not possible with any .NET compiler. Method groups are used for code analysis by compilers and tools such as ReSharper, but they are not something that actually exists at runtime.

0
Be Brave Be Like Ukraine On

If your functions take their arguments in tupled form, like these:

let f1 (s: string, b: bool) =
    System.Threading.Thread.Sleep 1000
    s

let f2 (n: int, s:string, dt: System.DateTime) =
    System.Threading.Thread.Sleep 1000
    n+1

then the implementation becomes trivial:

let Timed f args =
    let sw = System.Diagnostics.Stopwatch.StartNew()
    let ret = f args
    printfn "Called with arguments %A, elapsed %A" args sw.Elapsed 
    ret

Usage:

f1
|> Timed // note, at this time we haven't yet applied any arguments
<| ("foo", true)
|> printfn "f1 done, returned %A"

f2
|> Timed
<| (42, "bar", DateTime.Now)
|> printfn "f2 done, returned %A"

However, if the functions take their arguments in curried form, like this:

let f1Curried (s: string) (b: bool) =
    System.Threading.Thread.Sleep 1000
    s

let f2Curried (n: int) (s:string) (dt: System.DateTime) =
    System.Threading.Thread.Sleep 1000
    n+1

it becomes a bit tricky. The idea is using standard operators (<|), (<||), and (<|||) that are intended to uncurry the arguments.

let Timed2 op f args =
    let sw = System.Diagnostics.Stopwatch.StartNew()
    let ret = op f args
    printfn "Called with arguments %A, elapsed %A" args sw.Elapsed 
    ret

f1Curried
|> Timed2 (<||) // again, no arguments are passed yet
<| ("foo", true)
|> printfn "f1Curried done, returned %A"

f2Curried
|> Timed2 (<|||)
<| (42, "bar", DateTime.Now)
|> printfn "f2Curried done, returned %A"
0
Mark Seemann On

Why not just do something like this?

let getTime f =
    let sw = Stopwatch.StartNew()
    let result = f ()
    printfn "%A" sw.Elapsed
    result

Assuming that f1, f2, and f3 are three functions that take respectively 1, 2, and 3 arguments, you can use the getTime function like this:

getTime (fun () -> f1 "foo")
getTime (fun () -> f2 "foo" "bar")
getTime (fun () -> f3 "foo" "bar" "baz")

However, if you just need to time some functions in FSI, this feature is already built-in: just type

> #time;;

and timing will be turned on.