Can anyone explain these basic partial application issues?

I have some very basic code which works:

let randomNumberGenerator = System.Random.Shared

let randomFraction = (fun () -> randomNumberGenerator.NextDouble())

let shuffle (randomNumber : unit -> float) (data: 'a list) = data |> List.sortBy (fun _ -> randomNumber())

let shuffler data = shuffle randomFraction data

[ 1; 2; 3; 4; 5; 6 ] |> shuffler

If I remove the ‘data’ parameter from the end of the shuffler function definition, e.g. let shuffler data = shuffle randomFraction – I don’t get a compiler error but I get this when I run the code via the FSI:

Scratchpad3.fsx(14,1): error FS0030: Value restriction. The value ‘it’ has been inferred to have generic type
val it: ('_a list → '_a list)
Either make the arguments to ‘it’ explicit or, if you do not intend for it to be generic, add a type annotation.

If I then remove the ‘data’ from the shuffler parameters, e.g. let shuffler = shuffle randomFraction - the code works again.

However, if I then comment out the last line, where the shuffler function is being used, I get a compiler error:

Severity Code Description Project File Line Suppression State
Error FS0030 Value restriction. The value ‘shuffler’ has been inferred to have generic type
val shuffler: ('_a list → '_a list)
Either make the arguments to ‘shuffler’ explicit or, if you do not intend for it to be generic, add a type annotation. F# Miscellaneous Files \Scratchpad3.fsx 12 Active

This seems odd to me but should I be expecting it?

There’s probably a perfectly reasonable explanation but I don’t understand why the shuffler function should become ‘invalid’ when it’s not being ‘used’.

A function can be generic, but a “plain value” can’t. let shuffler = shuffle randomFraction is a plain value because it doesn’t have a function parameter written out. This is true even though the value is ultimately a function! I believe it’s a limitation of the .NET type system that F# wants to remain compatible with.

So when the shuffler value is actually used on an int list then the compiler can infer the generic type argument to be int and make shuffler concrete, not generic. When the usage is removed, it doesn’t have a way to infer the type argument, and it can’t store a generic value, and so there’s a compiler error.

Another option you have is to add an explicity type argument to shuffle:

let shuffle<'a> (randomNumber : unit -> float) (data: 'a list) = data |> List.sortBy (fun _ -> randomNumber())

let shuffler<'a> = shuffle<'a> randomFraction

This means explictly passing the argument all the way through to the shuffle function as well. Now shuffler is actually more of a function than a value because it doesn’t run until a type argument is provided. Here it is still generic, and doesn’t need to be called at all.

That was my attempt to explain it! Hope that it helps. More info can be found in these links:

Thanks for that explanation.

Might take me a while to get it to ‘sink in’.