Dictionary 如何最好地仅基于参数而不是函数闭包,并在类内部进行记忆?
(编辑并重写问题以反映聊天讨论结果) 在一行中:给定一个状态单子中的一个状态,计算一次单子函数,缓存结果 我试图缓存函数求值的结果,其中缓存的键是状态单子的状态,并且我不关心可能的副作用:即,即使函数体在理论上可能发生变化,我知道它将独立于状态:Dictionary 如何最好地仅基于参数而不是函数闭包,并在类内部进行记忆?,dictionary,reference,f#,heap,memoization,Dictionary,Reference,F#,Heap,Memoization,(编辑并重写问题以反映聊天讨论结果) 在一行中:给定一个状态单子中的一个状态,计算一次单子函数,缓存结果 我试图缓存函数求值的结果,其中缓存的键是状态单子的状态,并且我不关心可能的副作用:即,即使函数体在理论上可能发生变化,我知道它将独立于状态: f x = state { return DateTime.Now.AddMinutes(x) } g x = state { return DateTime.Now.AddMinutes(x) } 在这里,G10和F10应该产生相同的结果,它们可能
f x = state { return DateTime.Now.AddMinutes(x) }
g x = state { return DateTime.Now.AddMinutes(x) }
在这里,G10
和F10
应该产生相同的结果,它们可能不会因为双重调用DateTime而有所不同。现在,它们必须是确定性的。为了便于论证,这里的变量状态是x
同样地,(g10)-(f5)
应该精确地产生5分钟
,而不是多多少少一微秒
在发现缓存不起作用后,我使用with maps(或dict)将一个更复杂的解决方案调低到最低限度
记忆模式:
module Cache =
let cache f =
let _cache = ref Map.empty
fun x ->
match (!_cache).TryFind(x) with
| Some res -> res
| None ->
let res = f x
_cache := (!_cache).Add(x,res)
res
缓存应该作为计算生成器的一部分使用,在Run方法中:
type someBuilder() =
member __.Run f =
Log.time "Calling __.Run"
let memo_me =
fun state ->
let res =
match f with
| State expr - expr state
| Value v -> state, v
Log.time ("Cache miss, adding key: %A", s)
res
XCache.cache memo_me
这不起作用,因为由于闭包,每次缓存函数都不同,导致每次都命中缓存未命中。它应该独立于上面的expr
,并且只依赖于状态
我尝试将\u缓存
放在模块级的缓存函数之外,但随后遇到了泛化问题:
值限制。已推断值“\u cache”具有泛型类型
将“_cache”定义为一个简单的数据项,使其成为具有显式参数的函数,或者,如果不希望它是泛型的,则添加一个类型注释
然后我尝试使用类型注释来解决这个问题,但由于同样的原因,我最终无法在泛型函数中使用它:它需要使用特定的类型注释:
let _cache<'T, 'U when 'T: comparison> ref : Map<'T, 'U> = ref Map.empty
在FSI中运行示例的结果:
跑
给我打电话
valit:(float*int)*字符串=((42.0,42),“测试我的值”)
给我打电话
valit:(float*int)*字符串=((42.0,42),“测试我的值”)
只需将缓存添加到运行中
member __.Run f =
printfn "Running!"
Cache.cache <|
fun s ->
match f with
| RS.Expression (State state) ->
printfn "Call me once!"
state s
| RS.Value v -> s, v
输出是
Call me once!
to cache
val it : (float * int) * string = ((42.0, 42), "test my value")
>
from cache
val it : (float * int) * string = ((42.0, 42), "test my value")
按照您的设置方式,缓存不会存储在运行之间的任何位置。您需要在类中创建一个局部变量。@JohnPalmer:看来,无论我试图以何种方式转换它,我要么遇到了泛型问题(“表达式的类型应该是obj
,但这里有'a*'b
”,要么类似的说法是(int*int)
不支持IComparable
)。我听从了你的建议,但我找不到一种方法使它工作,似乎只要我尝试在模块级使用let语句来捕获可记忆的函数,泛型就被杀死了。与此类似,但不同的是:你能提供使用计算生成器的代码吗?@FyodorSoikin,我将尝试创建一个可管理且规模较大的示例。到目前为止,我的运行理论是,你实际上要对Run
进行多次调用,而不是只进行一次调用,然后重复调用结果。但是让我们看看这个例子。谢谢你。看起来,Cache.Cache实际上只创建了一个生成器,除非您多次调用new builder()
,当然您不必这样做,否则您可以对所有计算使用相同的实例。
module Cache =
let cache f =
let _cache = ref Map.empty
fun x ->
match (!_cache).TryFind(x) with
| Some res -> printfn "from cache"; res
| None ->
let res = f x
_cache := (!_cache).Add(x,res)
printfn "to cache"
res
Call me once!
to cache
val it : (float * int) * string = ((42.0, 42), "test my value")
>
from cache
val it : (float * int) * string = ((42.0, 42), "test my value")