Search code examples
genericsloggingf#inline

Make wrapped F# SRTP function generic


I have a generic function using statically resolved type parameters

let inline divide a b = a / b

with signature ^a -> ^a -> ^a

I can create a wrapper function

let log f = 
    let result = f()
    printfn "Result: %A" result
    result

If I then create a function like

let loggedDivide a b = log (fun () -> divide a b)

its signature is float -> float -> float instead of ^a -> ^a -> ^a, meaning

loggedDivide 2.0 5.0
loggedDivide 2 5 //error

How can this be done?

Note, something like this misses the point of trying to reuse functions

let logValue a = printfn "Result: %A" a
divide 2.0 5.0 |> logValue
divide 2 5 |> logValue

And things doesn't remain generic this way

let logValueAndReturn a = 
    printfn "Result: %A" a
    a

let divideAndLog a b = divide a b |> logValue
divideAndLog 2.0 5.0
divideAndLog 2 5 //error

Solution

  • You have to make your derived function inline as well:

    let inline loggedDivide a b = log (fun () -> divide a b)
    

    That will allow constraints to be propagated:

    val inline loggedDivide :
      a: ^a -> b: ^b ->  ^c
        when ( ^a or  ^b) : (static member ( / ) :  ^a *  ^b ->  ^c)
    

    The reason for this is that SRTP's is an F# compiler feature, which is resolved at compile-time, so functions get specialized by inlining at the call site.

    If you want your function to remaing generic, it has to be inline.

    Note that the reason you get your function inferred as operating with int is because that's the default for \ and other math operators. Otherwise you would get an error indicating ambiguity.