考虑一个递归函数,比如由以下定义的Euclid算法:
let rec gcd a b =
let (q, r) = (a / b, a mod b) in
if r = 0 then b else gcd b r
(这是一个简化的,非常脆弱的定义。)如何记忆这样的功能?定义高阶函数的经典方法memoize : ('a -> 'b) -> ('a -> 'b)
添加memoization到函数是没用的,因为它只会节省第一次调用的时间。
我已经找到了如何在Lisp或Haskell中记忆这样的函数的细节:
这些建议依赖于Lisp中的能力来覆盖函数的符号定义或Haskell使用的“按需调用”策略,因此在OCaml中无用。
答案 0 :(得分:5)
获胜策略是在延续传递样式中定义要记忆的递归函数:
let gcd_cont k (a,b) =
let (q, r) = (a / b, a mod b) in
if r = 0 then b else k (b,r)
我们不是递归地定义gcd_cont
函数,而是添加一个参数,即要继续调用“continuation”而不是递归。现在我们定义两个高阶函数call
和memo
,它们对具有continuation参数的函数进行操作。第一个函数call
定义为:
let call f =
let rec g x =
f g x
in
g
它构建了一个函数g
,它没有任何特殊功能,只能调用f
。第二个函数memo
构建了一个实现memoization的函数g
:
let memo f =
let table = ref [] in
let compute k x =
let y = f k x in
table := (x,y) :: !table; y
in
let rec g x =
try List.assoc x !table
with Not_found -> compute g x
in
g
这些功能具有以下签名。
val call : (('a -> 'b) -> 'a -> 'b) -> 'a -> 'b = <fun>
val memo : (('a -> 'b) -> 'a -> 'b) -> 'a -> 'b = <fun>
现在我们定义gcd
函数的两个版本,第一个没有memoization,第二个带memoization:
let gcd_call a b =
call gcd_cont (a,b)
let gcd_memo a b =
memo gcd_cont (a,b)
答案 1 :(得分:1)
# let memoize f =
let table = Hashtbl.Poly.create () in
(fun x ->
match Hashtbl.find table x with
| Some y -> y
| None ->
let y = f x in
Hashtbl.add_exn table ~key:x ~data:y;
y
);;
val memoize : ('a -> 'b) -> 'a -> 'b = <fun>
# let memo_rec f_norec x =
let fref = ref (fun _ -> assert false) in
let f = memoize (fun x -> f_norec !fref x) in
fref := f;
f x
;;
val memo_rec : (('a -> 'b) -> 'a -> 'b) -> 'a -> 'b = <fun>
您应该阅读 Real World OCaml 一书中的https://realworldocaml.org/v1/en/html/imperative-programming-1.html#memoization-and-dynamic-programming部分。
它将帮助您真正了解memo
的工作原理。