F # закрытие: ссылка на ячейку карты всегда пуста - PullRequest
1 голос
/ 27 сентября 2010

С учетом следующего кода:

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

... значение ячейки ссылки dataMap в первой строке определения функции (т.е. fun _key -> ...) getDataFn <'T> всегда пусто (т.е. dataMap.Count = 0), нет независимо от того, сколько раз я вызываю getIndexNumber.

Очевидно, я ожидаю, что dataMap будет обновляться всякий раз, когда несуществующий ключ передается в getIndexNumber, но этого не происходит. Пустая карта каждый раз передается в getIntValue.

Почему это происходит?

(П.С. Я знаю, что могу просто использовать словарь, это не относится к делу.)

1 Ответ

3 голосов
/ 27 сентября 2010

Вот код, чтобы разблокировать вас:

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

Ключ в том, что getIndexNumber является свойством, которое переоценивается каждый раз, когда оно вызывается, что в итоге вызывает getDataFn каждый раз, что выделяет новый ref каждый раз. Я переместил вызов на getDataFn в static let, чтобы он вызывался только один раз.

Этот код выглядит очень не идиоматично. Могу я предложить код, похожий на этот?

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