Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why explicitly generic function value binds differently from similar non-generic?

Tags:

generics

f#

Let's take a simple function value f1:

let f1 = printfn "*bind f1*"; fun () -> printfn "f1()"

f1 binds in FSI as

*bind f1*
val f1 : (unit -> unit)

and, being invoked, behaves as expected

> () |> f1 |> f1;;
f1()
f1()
val it : unit = ()

Now let's take a similar function value, but made explicitly generic f2<'a>:

let f2<'a> = printfn "*bind f2*"; fun () -> printfn "f2()"

f2 binds in FSI as

val f2<'a> : (unit -> unit)

without any *bind f2* output whatsoever, but then, being invoked, outputs it on each f2 invocation:

> () |> f2 |> f2;;
*bind f2*
f2()
*bind f2*
f2()
val it : unit = ()

My question is: what may be the cause for such observed disparity?

like image 870
Gene Belitski Avatar asked Oct 28 '13 05:10

Gene Belitski


2 Answers

F# does not generally allow creating of generic values because of the "value restriction" difficulties that this introduces (that is, you cannot create a syntactic value that is generic, even if it is a code that returns function). So, your f2 should not be allowed, but...

The rule has a single exception - it is often useful to have generic values like List.empty and so if you declare a value with explicit generic type arguments, it actually is compiled into a function that returns the result.

That's exactly what happens in your example:

let f2<'a> = printfn "*bind f2*"; fun () -> printfn "f2()"

here, f2 is a generic value (even though it does not really use the type argument anywhere), but it has an explicit generic type argument and so it is actually compiled into a method that is called each time f2 is accessed and it returns the result (function unit -> unit)

I could not find any clear explanation for this in the specification, but there is a good MSDN article (see "Case 4") and also a blog by former F# team member.

like image 102
Tomas Petricek Avatar answered Nov 15 '22 08:11

Tomas Petricek


Tomas is right, as usual. Note that you can create a generic function that behaves as f1 does, but you've got to use a nominal generic type as a cache of sorts:

type private Container<'a> () =
    static member val f2 : unit -> unit = printfn "*bind f2*"; fun () -> printfn "f2()"

let f2<'a>() = Container<'a>.f2()
like image 33
kvb Avatar answered Nov 15 '22 07:11

kvb