我想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?