views:

96

answers:

1

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.)

+3  A: 

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
Brian
Thanks for the answer, Brian. I'm still trying to wrap my head around partial application, and this is now the second time where I've created a property where I thought I was creating a function.
MiloDC
Note: In the interest of learning more of the purely functional side of F#, I'm trying to do all of this without resorting to side effects, which Brian's code does (as would any code involving Dictionary.Add, which is why I'm avoiding that method, as well). I've green-checked his answer because he did answer my question about why my code wasn't working, but I'd appreciate it if anyone could show me how to do this without using side effects.
MiloDC
The `ref` cell and the `:=` are effects, too. If you want to be completely effect-free, you'll have to put the `Map` back in the hands of the caller; as it stands, `GetIndexNumber` is not quite referentially transparent, due to the state and the interaction with `doSomething`. It would be interesting to know the bigger-picture context of what you're trying to do, to offer advice.
Brian
Never mind what I wrote, my original code has a side effect in it, anyway (i.e. Map.add). :) P.S. Of course my code works if I put it inside a module instead of a class, although I naturally lose the ability to overload my functions.
MiloDC