3

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?

Bellarmine Head
  • 3,397
  • 2
  • 22
  • 31
  • Replacing the class with a module, the same behaviour obtains. On decompiling, the generated static class contains `getValFromMapPartial` as a method and `getValFromMapPartialAndTacit` as a property. Maybe this is a known gotcha, but if so, should the compiler warn? – Bellarmine Head Nov 20 '14 at 15:07

2 Answers2

3

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 in getValFromMapPartialAndTacit 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 = and fun, they would be evaluated at the point of the definition of f, 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:

let getValFromMapPartialAndTacit = getValFromMap !someMap

Here, the dereference operator (!) is applied when the value is bound, not when the function is called. it is equivalent to:

let mapRightNow = !someMap
let getValFromMapPartialAndTacit = getValFromMap mapRightNow
Vandroiy
  • 6,163
  • 1
  • 19
  • 28
  • I think that between you and Marc Sigrist, you've probably nailed it. I will go away and think deeply on this stuff, and make sure I understand it. Thanks. – Bellarmine Head Nov 20 '14 at 17:29
  • You have indeed clarified things with this edit. I'm beginning to get a better feel for this stuff. – Bellarmine Head Nov 21 '14 at 08:12
  • On reflection, Vandroiy's answer better describes the situation by distinguishing between let bindings of functions with params and values w/o params. So am now marking this as answer. But thx to you both for your answers; my understanding of F# has taken a significant step forwards. – Bellarmine Head Nov 22 '14 at 09:47
2

getValFromMapPartial is a true syntactic function. Its signature is val getValFromMapPartial : key:string -> int. Whenever it is called, it uses the current value of someMap. That's why it works in your example; it accesses the version of someMap who has an entry.

On the other hand, getValFromMapPartialAndTacit is a lambda-computing function. Its signature is val getValFromMapPartialAndTacit : (string -> int) (notice the parentheses). The lambda has a compiler-generated closure, which contains the version of someMap at the time the lambda was computed. That's why it does not work in your example; it always acesses the same, original version of someMap who has no entry.

Marc Sigrist
  • 3,964
  • 3
  • 22
  • 23
  • 1
    Worth to mention that even if variable `someMap` declared as mutable, the Map object itself in F# is immutable. So `addToMap` function returns Map object **different** from one passed to it as a parameter. – Petr Nov 20 '14 at 16:18
  • @Petr - sure, so I knew to assign the return value of `addToMap` back to `someMap`. Naturally I like to avoid mutability as much as possible, but in my project (from which the above repro was derived) I have a map of users to which new users can be added. – Bellarmine Head Nov 20 '14 at 16:40
  • Now I know what to look for, I see that "true syntactic function" is mentioned in the Expert F# 3.0 book. A cursory read indicates that having explicit arguments solves various problems. Maybe avoiding the tacit style altogether is generally A Good Thing in order to avoid various problems... – Bellarmine Head Nov 20 '14 at 16:57
  • Seeing as the lambda has a compiler-generated closure, I switched `someMap` from being mutable to being a reference cell but the call to `getValFromMapPartialAndTacit` *still* fails! – Bellarmine Head Nov 20 '14 at 17:04
  • @AndrewWebb Have a good look at where the cell is dereferenced (!). If this happens inside a *value* instead of a *function*, you implicitly cache the *content* of the cell instead of the *identity* of the cell. – Vandroiy Nov 20 '14 at 17:07
  • @Vandroiy - `let getValFromMapPartialAndTacit = getValFromMap !someMap` – Bellarmine Head Nov 20 '14 at 17:12
  • @MarcSigrist - I checked and double-checked that the types of the two functions were both (string -> int) before posting, and *in the class case* they are! But in the module case, they are different, as you state. Confusing! – Bellarmine Head Nov 20 '14 at 17:49
  • 1
    @AndrewWebb I don't think the parentheses in the signatures are the right place to look. (This part of this answer might be wrong.) The important distinction is whether code is part of a value definition or a function definition. I've edited my answer again to try to clarify this. – Vandroiy Nov 20 '14 at 22:23
  • 1
    @Andrew In the class case, we still have the behavior of a syntactical function, but it is implemented internally by the compiler by means of a lambda value (without capturing someMap in a closure). That's fascinating, and potentially confusing. However, it's a compiler-internal implementation detail with no practical consequence. I became aware of this detail for the first time only when I saw your example. – Marc Sigrist Nov 21 '14 at 12:43