我正在尝试记住一个类的成员函数,但每次调用该成员(由另一个成员)时,它都会创建一个全新的缓存和“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的内部递归似乎也为每个级别设置了一个新的“缓存”。
我做错了什么,我怎样才能做到这一点?
答案 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 = ...
在这里,右侧只评估一次,但一切都是独立的。