F# closure: Map reference cell is always empty
Given the following code:
static member private getIntValue (_map:Map<string, int>) (_key:string) =
if (_map.ContainsKey _key) then
_map.[_key], _map
else
let i = doSomething ()
i, if i > 0 then _map.Add (_key, i) else _map
static member private getDataFn<'T> (_getFn:Map<string, 'T> -> string -> 'T * Map<string, 'T>) =
let dataMap = ref Map.empty
fun _key ->
let value, updatedMap = _getFn !dataMap _key
dataMap := updatedMap
value
static member getIndexNumber = getDataFn<int> getIntValue
... the value of the dataMap reference cell in the first line of the function definition (i.e. fun _key -> ...) of getDataFn<'T> is always empty (i.e. dataMap.Count = 0), no matter how many times I call getIndexNumber.
Obviously, I'm expecting dataMap to be updated whenever a non-existent key is passed to getIndexNumber, but this isn't happening. An empty Map is being passed to getIntValue every开发者_运维百科 time.
Why is this happening?
(P.S. I know that I could just use a Dictionary, that's beside the point.)
Here's some code to unblock you:
let doSomething() = 2
type Foo() =
static let indexer = Foo.getDataFn<int> Foo.getIntValue
static member private getIntValue (_map:Map<string, int>) (_key:string) =
if (_map.ContainsKey _key) then
_map.[_key], _map
else
let i = doSomething ()
i, if i > 0 then _map.Add (_key, i) else _map
static member private getDataFn<'T> (_getFn:Map<string, 'T> -> string -> 'T * Map<string, 'T>) : (string -> 'T)=
let dataMap = ref Map.empty
fun _key ->
printfn "count = %d" (!dataMap).Count
let value, updatedMap = _getFn !dataMap _key
dataMap := updatedMap
value
static member getIndexNumber = indexer
// static member getIndexNumber = Foo.getDataFn<int> Foo.getIntValue
let r = Foo.getIndexNumber("foo")
printfn "%d" r
let r2 = Foo.getIndexNumber("foo")
printfn "%d" r2
The key is that getIndexNumber
is a property, which is re-evaluated each time it's called, which ends up calling getDataFn
each time, which allocates a fresh ref
each time. I moved the call to getDataFn
into a static let
so it only gets called once.
This code all looks very un-idiomatic, though. Might I suggest code more like this?
let doSomething() = 2
type StringIndexer() =
let mutable map : Map<string,int> = Map.empty
let get(s) =
printfn "count = %d" map.Count
match map.TryFind s with
| None ->
let i = doSomething()
map <- map.Add(s,i)
i
| Some(i) -> i
member this.GetIndexNumber(s) = get(s)
let si = new StringIndexer()
let r = si.GetIndexNumber("foo")
printfn "%d" r
let r2 = si.GetIndexNumber("foo")
printfn "%d" r2
精彩评论