F# partial application function wrapper does not keep return type generic for parameter function

193 Views Asked by At

TL DR: I am trying to create a function wrapper. The wrapped function takes no parameter but returns a value.

The reason I want to do this is to create a function similar to "lock" but for a semaphore.

Here it is

let functionWrapper (param1) (f:(unit -> 'a)) =
    printfn "Calling function %s" param1
    f()

Signature : (string -> (unit -> 'a) -> 'a)
So far, all good

Now let's enrich functionWrapper with a parameter

let functionWrapperFoo = functionWrapper "Foo"

Signature : ((unit -> obj) -> obj)
For a reason, 'a becomes obj here..

Now I finally want to use this function to wrap another one.
But the type inference will change the signature of functionWrapperFoo into ((unit -> int) -> int) which prevents from using the function with other types afterward.

let resultFooInt =
    functionWrapperFoo (
        fun _ ->
            (42)
        )


// Because I am using a string as a return type now, this one bellow won't compile
let resultFooString =
    functionWrapperFoo (
        fun _ ->
            "42"
        )

Why I can't keep this function generic after the first partial application ?

I did find a workaround but I really do not understand the reason for this.

I've seen some post mentioning something similar about partial application but in my case my wrapped function does not have parameters
Keeping partially applied function generic

Full original code

let functionWrapper (param1) (f:(unit -> 'a)) =
    printfn "Calling function %s" param1
    f()

let functionWrapperFoo = functionWrapper "Foo"
let resultFooInt =
    functionWrapperFoo (
        fun _ ->
            (42)
        )
let resultFooString =
    functionWrapperFoo (
        fun _ ->
            "42"
        )

Workaround

let functionWrapper (param1) (f:(unit -> 'a)) =
    printfn "Calling function %s" param1
    f()

let functionWrapperFoo() = functionWrapper "Foo"

let resultFooInt =
    functionWrapperFoo() (
        fun _ ->
            (42)
        )
    
let resultFooString =
    functionWrapperFoo() (
        fun _ ->
            "42"
        )
2

There are 2 best solutions below

0
Tomas Petricek On BEST ANSWER

This is an issue known as value restriction. There is a fairly detailed explanation of this in this article on automatic generalization in F# and there are also good existing answers on SO.

The basic issue is that F# does not let you define generic syntactic values. Your functionWrapperFoo is defined as a value, i.e. using let value = <expr>. In this case, the value cannot be generic, even if the expression <expr> actually evalautes to a function.

Your workaround works, because it makes the syntactic declaration into a function declaration using let func arg = <expr>. Here, the compiler knows for sure that it is going to be a function.

As mentioned by Tom, a better workaround is to just add a parameter to your functionWrapperFoo definition and pass it to functionWrapper - that way, it will be syntactically defined as a function and automatic generalization will make it generic.

0
Tom Moers On

If you define f as a value in your first full code section, then f becomes fully typed and thus is no longer generic. The compiler needs to infer some type.

If you make keep it a function as in your workaround section. It remains a (generic) function, which is why it works.

You can also specify the function argument again, which would make the calling functions less ugly:

let inline functionWrapper (param1) (f:(unit -> 'a)) =
    printfn "Calling function %s" param1
    f()

let functionWrapperFoo f = functionWrapper "Foo" f

let resultFooInt =
    functionWrapperFoo (
        fun _ ->
            (42)
        )
let resultFooString =
    functionWrapperFoo (
        fun _ ->
            "42"
        )

Related Questions in F#