我有一些F#代码可以缓存未来查找的结果。我的理解是你添加的词典和其他数据结构需要副作用。 (即改变字典的状态)这是正确的吗?这被认为是不纯的还是仍然在无副作用计算的模型中。
let rec fib =
let dict = new System.Collections.Generic.Dictionary<_,_>()
fun n ->
match dict.TryGetValue(n) with
| true, v -> v
| false, _ ->
let fibN =
match n with
| 0 | 1 -> n
| _ -> fib (n - 1) + fib(n - 2)
dict.Add(n, fibN)
fibN
答案 0 :(得分:5)
要重述注释中提到的内容,您可以将memoization提取到更高阶的函数中,该函数将返回作为参数传入的函数的memoized版本:
let memoize f =
let dict = new System.Collections.Generic.Dictionary<_,_>()
fun x ->
match dict.TryGetValue(n) with
| true, v -> v
| false, _ ->
let v = f x
dict.Add(x, v)
v
通过这样做,你实际上可以使memoized函数纯净,并且memoization是一个实现细节。你发布的代码,这两个问题纠缠在一起,并不像理所当然那么容易理解。
请注意,记忆递归函数有点棘手 - 您需要以一种有助于记忆的方式构造函数以进行memoize。
此处的另一个问题是您可能遇到的并发问题。要解决这些问题,您可以锁定dict.Add
:
...
let v = f x
lock dict <| fun () ->
if not <| dict.ContainsKey(x) then
dict.Add(x, v)
v
或代替Dictionary
有一个ref
单元格持有Map
(在这种情况下,您可能遇到的任何并发问题仍然存在,但本质上不再是灾难性的。)< / p>
答案 1 :(得分:1)
memoized函数存储结果,因此它不必在后续调用中使用相同的参数计算结果。存储结果的事实是副作用,它也是memoized函数的定义属性。因此,我的结论是,你的问题的答案是&#34;没有。&#34;
解决关于在dict中存储错误值的评论;是的,你是对的,但是另一个问题并不是不正确的结果。 Dictionary类不是线程安全的。如果两个线程同时尝试读取和/或写入字典,则可能会出现异常。例如:
let data = [| 1 .. 20 |]
let fibs = data |> Array.Parallel.map fib
当我在F#interactive中多次运行时,我没有得到任何异常,但是在某些更改的情况下,我得到了一个System.ArgumentException:
已添加具有相同键的项目。
这些变化是这些;在每种情况下,我在第一次或第二次尝试时都得到了例外:
printfn
uint64
(删除printfn
诊断)float
(即System.Double)bigint