F#:尝试memoize成员函数重置每次调用缓存?

时间:2012-08-07 11:47:08

标签: caching f# member memoization

我正在尝试记住一个类的成员函数,但每次调用该成员(由另一个成员)时,它都会创建一个全新的缓存和“memoized”函数。

member x.internal_dec_rates = 
        let cache = new Dictionary< Basis*(DateTime option), float*float>()
        fun (basis:Basis) (tl:DateTime option) ->
            match cache.TryGetValue((basis,tl)) with
            | true, (sgl_mux, sgl_lps) -> (sgl_mux, sgl_lps)
            | _ ->
                let (sgl_mux, sgl_lps) =
                    (* Bunch of stuff *)
                cache.Add((basis,tl),(sgl_mux,sgl_lps))
                sgl_mux,sgl_lps

我在“Real World Functional Programming”中使用清单10.5作为模型。我尝试过使用memoization高阶函数,这没有用。上面的列表直接内置了memoization。

问题是,当我打电话时,例如

member x.px (basis:Basis) (tl: DateTime option) = 
        let (q,l) = (x.internal_dec_rates basis tl)
        let (q2,l2) = (x.internal_dec_rates basis tl)
        (exp -q)*(1.-l)

执行进入'let cache = ...'行,击败了整点。我放入(q2,l2)行以确保它不是范围问题,但它似乎不是。

事实上,我使用Petricek的代码作为成员函数进行了测试,这看起来有同样的问题:

// Not a member function
let memo1 f =
    let cache = new Dictionary<_,_>()
    (fun x ->
        match cache.TryGetValue(x) with
        | true, v -> v
        | _ -> let v = f x
               cache.Add(x,v)
               v
    )

member x.factorial = memo1(fun y->
    if (y<=0) then 1 else y*x.factorial(y-1))

即使是x.factorial的内部递归似乎也为每个级别设置了一个新的“缓存”。

我做错了什么,我怎样才能做到这一点?

6 个答案:

答案 0 :(得分:6)

回应你对杰克答案的评论,这不必变得单调乏味。给出一个memoize函数:

let memoize f =
  let cache = Dictionary()
  fun x ->
    match cache.TryGetValue(x) with
    | true, v -> v
    | _ -> 
      let v = f x
      cache.Add(x, v)
      v

将每个函数定义为let-bound值,并从方法中返回它们:

type T() as x =
  let internalDecRates = memoize <| fun (basis: Basis, tl: DateTime option) ->
    (* compute result *)
    Unchecked.defaultof<float * float>

  let px = memoize <| fun (basis, tl) ->
    let (q,l) = x.InternalDecRates(basis, tl)
    let (q2,l2) = x.InternalDecRates(basis, tl)
    (exp -q)*(1.-l)

  member x.InternalDecRates = internalDecRates
  member x.Px = px

唯一的“样板”是let绑定并调用memoize

编辑:正如kvb所说,在F#3.0中,自动属性允许更简洁的解决方案:

type T() as x =
  member val InternalDecRates = memoize <| fun (basis: Basis, tl: DateTime option) ->
    (* compute result *)
    Unchecked.defaultof<float * float>

  member val Px = memoize <| fun (basis, tl) ->
    let (q,l) = x.InternalDecRates(basis, tl)
    let (q2,l2) = x.InternalDecRates(basis, tl)
    (exp -q)*(1.-l)

答案 1 :(得分:5)

我在这里看到很多很长的答案;简短的回答是

member x.P = code()

定义了一个属性P,它具有 getter ,每次访问code()时都会运行P。您需要将缓存创建移动到类的构造函数中,以便它只运行一次。

答案 2 :(得分:4)

正如其他人已经说过的那样,仅仅通过在F#2.0中定义单个member就无法做到这一点。您需要一个单独的字段(let绑定值)用于缓存或记忆的本地函数。

如kvb所述,在F#3.0中,您可以使用member val来执行此操作,open System.Collections.Generic type Test() = /// Property that is initialized when the object is created /// and stores a function value 'int -> int' member val Foo = // Initialize cache and return a function value let cache = Dictionary<int, int>() fun arg -> match cache.TryGetValue(arg) with | true, res -> res | false, _ -> let res = arg * arg printfn "calculating %d" arg cache.Add(arg, res) res // Part of the property declaration that instructs // the compiler to generate getter for the property with get 是在创建对象时初始化的属性(并且具有自动生成的支持字段,其中存储结果)。以下是演示此内容的完整示例(它将在Visual Studio 2012中使用):

with get

声明的with get, set部分可以省略,但我在此处包含它以使示例更清晰(您也可以使用test.Foo来获取可变属性)。现在,您可以将let t = Test() t.Foo(10) t.Foo(10) 作为函数调用,并根据需要缓存值

t.Foo

这种方法的唯一问题是FSharpFunc<int, int>实际上被编译为返回函数的属性(而不是编译为方法)。当你使用F#中的类时,这不是一个大问题,但如果你从C#调用它会是一个问题(因为C#会将成员看作类型{{1}}的属性,这很难使用)。

答案 3 :(得分:2)

John是正确的 - 您需要将cache字典移动到该类型的私有绑定成员中。

类型成员的编译方式与let - 模块中的绑定值略有不同,这是行为差异的原因。如果您复制/粘贴x.internal_dec_rates方法的主体并将其分配给模块中的let绑定值,那么应该正常工作,因为F#编译器将将其编译为一个闭包,它将被创建一次,然后分配给模块的static readonly字段。

其他一些提示,好的措施:

  • 类型member方法可以使用可选参数 - 因此,如果您愿意,可以稍微简化方法签名。
  • 您只需创建一次缓存密钥并重复使用(这也有助于避免错误)。
  • 您可以通过为元组指定一个名称(例如(sgl_mux, sgl_lps))来简化value模式匹配代码,因为您无论如何都只是返回整个元组。

以下是我对您的代码的看法:

type FooBar () =
    let cache = new Dictionary< Basis*(DateTime option), float*float>()

    member x.internal_dec_rates (basis : Basis, ?tl : DateTime) =
        let key = basis, tl
        match cache.TryGetValue key with
        | true, value -> value
        | _ ->
            // sgl_mux, sgl_lps
            let value =
                (* Bunch of stuff *)

            cache.Add (key, value)
            value

答案 4 :(得分:1)

你需要在函数调用之外移动字典 - 比如

let cache = new Dictionary< Basis*(DateTime option), float*float>()
member x.internal_dec_rates =             
        fun (basis:Basis) (tl:DateTime option) ->
            match cache.TryGetValue((basis,tl)) with
            | true, (sgl_mux, sgl_lps) -> (sgl_mux, sgl_lps)
            | _ ->
                let (sgl_mux, sgl_lps) =
                    (* Bunch of stuff *)
                cache.Add((basis,tl),(sgl_mux,sgl_lps))
                sgl_mux,sgl_lps

这样,缓存在函数调用中持续存在。您的memo1有同样的问题。在原始版本中,每次调用函数时都会创建一个新的缓存,这样我们只有一个缓存,它会在函数调用中持续存在。

答案 5 :(得分:1)

除了其他答案之外,请注意在F#3.0中,您可以使用自动实现的属性,这些属性将按您的需要运行:

member val internal_dec_rates = ...

在这里,右侧只评估一次,但一切都是独立的。