Consider the following F#:-
type TestClass() =
let getValFromMap m k = Map.find k m
let addToMap map k i = map |> Map.add k i
let mutable someMap : Map<string,int> = Map.empty
let getValFromMapPartial key = getValFromMap someMap key
let getValFromMapPartialAndTacit = getValFromMap someMap
member this.AddThenGet() =
someMap <- addToMap someMap "A" 10
let value = getValFromMapPartial "A"
printfn "Value from partial = %i" value // prints out
let value = getValFromMapPartialAndTacit "A" // throws
printfn "Value from partial and tacit = %i" value
[<EntryPoint>]
let main argv =
let test = TestClass()
test.AddThenGet()
0
Functions getValFromMapPartial
and getValFromMapPartialAndTacit
are, to my mind, identical. F# says they have the exact same type: (string -> int)
. And yet they behave very differently, and they are compiled very differently. Decompiling using dotPeek, I see that getValFromMapPartial
is a method, whereas getValFromMapPartialAndTacit
is a field that is initialized in the ctor.
F# does not complain about getValFromMapPartialAndTacit
, even on the highest warning level (both in VS 2012 and 2013). And yet calling this function in my sample above fails, presumably because it has wrapped the initial, empty version of the someMap
, despite its mutability.
Why is there a difference between these two functions? Should there be a warning from F# that the tacit / point-free version might fail?
The F# compiler distinguishes between let-bindings of functions, which have parameters, and values, which do not have parameters.
Value definition: A binding like
let a = ...
is a value definition. Its body is evaluated eagerly, "where it is", before the evaluation of anything further down the code.Function definition: A binding like
let f x = ...
is a syntactic function definition, the contents of which are evaluated when the function is called.Since
someMap
refers to a mutable variable, using this variable inside a function definition means reading from the variable when the function is called. However, the usage ingetValFromMapPartialAndTacit
reads the value at the moment of declaration.This behavior does not stop a value from being a function. You could just as well write
let f = fun x -> ...
to declare a function, and...
would again be part of a function definition. However, if you were to add definitions in between the=
andfun
, they would be evaluated at the point of the definition off
, not when it is called.In the question's comments, the same problem occurs with
someMap
being a mutable reference cell. This is the same problem. The function, as rewritten by Andrew for a mutable reference cell:Here, the dereference operator
(!)
is applied when the value is bound, not when the function is called. it is equivalent to: