This memoize function在运行时使用Null-Argument-Exception在() -> 'a
类型的任何函数上失败。
let memoize f =
let cache = System.Collections.Generic.Dictionary()
fun x ->
if cache.ContainsKey(x) then
cache.[x]
else
let res = f x
cache.[x] <- res
res
有没有办法编写一个也适用于() -> 'a
?
(我现在唯一的选择是使用Lazy
类型。调用x.Force()
来获取值。)
答案 0 :(得分:11)
函数失败的原因是F#表示使用类型()
的{{1}}的单位null
。字典不允许将unit
值作为键,因此失败。
在您的特定情况下,对null
类型的函数进行记忆没有太大意义(因为最好使用unit -> 'a
),但是在其他情况下这会是一个问题 - 例如lazy
也由None
表示,所以这也失败了:
null
解决此问题的简单方法是将密钥包装在另一种数据类型中,以确保它永远不会let f : int option -> int = memoize (fun a -> defaultArg a 42)
f None
:
null
然后你可以用type Key<'K> = K of 'K
构造函数包装密钥,一切都会很好地工作:
K
答案 1 :(得分:5)
我刚发现使用Map
而不是Dictionary
的最后一个memoize函数on the same website也适用于'a Option -> 'b
和() -> 'a
:
let memoize1 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
答案 2 :(得分:2)
具有纯函数(不仅仅是类型unit -> 'a
,而且还有任何其他函数)作为查找键的记忆是不可能的,因为函数通常没有reason的等式比较器。
对于这种特定类型的函数unit -> 'a
,似乎可能会出现一个自定义的相等比较器。但实现超出极限(反射,IL等)的比较器的唯一方法是调用查找函数f1 = f2 iff f1() = f2()
,这显然会使任何预期的性能改进无效。
所以,或许,正如已经指出的那样,对于这种情况,优化应围绕lazy
模式构建,而不是memoization
模式。
更新:事实上,在第二次查看问题之后,所有关于函数缺失等式的讨论都是正确的,但不适用,因为memoization发生在每个函数的闭包中的个体cache
内。另一方面,对于具有签名unit->'a
的这种特定类型的函数,即最多单个参数值,使用Dictionary
与大多数一个条目是一种矫枉过正。以下类似的有状态但更简单的实现只有一个memoized值将执行:
let memoize2 f =
let notFilled = ref true
let cache = ref Unchecked.defaultof<'a>
fun () ->
if !notFilled then
cache := f ()
notFilled := false
!cache
用作let foo = memoize2(fun () -> ...heavy on time and/or space calculation...)
首次使用foo()
执行并存储计算结果,所有后续foo()
只是重用存储的值。
答案 3 :(得分:-1)
使用可变字典和单字典查找调用的解决方案:
let memoize1 f =
// printfn "Dictionary"
let cache = System.Collections.Generic.Dictionary()
fun x ->
let result, value = cache.TryGetValue(x)
match result with
| true -> value
| false ->
// printfn "f x"
let res = f x
cache.Add(x, res)
res