F#:尝试memoize的成员函数每个调用重置缓存?(F#: Attempt to memoize

2019-09-20 23:15发布

我想memoize的一类的成员函数,但每一个成员被称为(由另一名成员)时它使一个全新的高速缓存和“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

我在“现实世界的函数编程”作为一种模式使用清单10.5。 我已经使用记忆化高阶函数尝试并没有帮助。 上述清单具有内置的直接记忆化。

问题是,当我把它如

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)

执行转到“让缓存= ...”行,战胜整点。 我把在(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的内部递归似乎建立了每个级别的新的“缓存”。

我在做什么错了,我怎样才能使这项工作?

Answer 1:

在回答关于杰克的回答您的意见,这并没有成为乏味。 给定一个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

定义你的每一个功能让绑定值和你的方法返回他们:

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)


Answer 2:

我看到很多在这里长的答案; 简单的答案是,

member x.P = code()

定义了一个属性P ,其具有在运行的吸气剂 code()每当P被访问。 您需要缓存的创建搬进类的构造函数,因此它只会运行一次。



Answer 3:

正如其他人已经说过了,这不能仅仅通过定义一个做member在F#2.0。 你要么需要一个单独的字段( let界值)的高速缓存或为其memoized局部功能。

正如KVB提到的,在F#3.0中,可以做到这一点使用member val其是在创建对象时被初始化(和具有其中结果被存储自动生成的支持字段)的性质。 下面是说明了这一点(这将在Visual Studio 2012的工作),一个完整的示例:

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

with get声明的一部分可以省略,但包括我在这里使样本更清晰(你也可以使用with get, set以获得一个可变的属性)。 现在,您可以拨打test.Foo的功能,根据需要将其缓存值

let t = Test()
t.Foo(10)
t.Foo(10)

这种方法唯一的问题是, t.Foo实际上编译成返回(而不是被编译为一个方法)的功能属性。 这不是一个大问题,当您使用从F#的类,但如果你是从C#调用它(因为C#会看到成员类型的属性这将是一个问题FSharpFunc<int, int>这是很难采用)。



Answer 4:

约翰是正确的-你需要移动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


Answer 5:

你需要移动词典功能外呼 - 像

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有同样的问题。 在最初的版本中,您创建一个新的缓存每次调用函数的时候,这样,我们只是有一个单一的缓存,在函数调用仍然存在。



Answer 6:

除了其他的答案,请注意,F#3.0,你可以使用自动实现的属性,只要你想,这将表现:

member val internal_dec_rates = ...

在这里,右侧只计算一次,但一切都自成体系。



文章来源: F#: Attempt to memoize member function resets cache on each call?