Как запомнить рекурсивные функции?
рассмотрим рекурсивную функцию, скажем, алгоритм Евклида, определенный:
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.
2 ответов
выигрышные стратегии является определение рекурсивной функции мемоизированную в продолжение прохождения стиле:
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
функция, мы добавляем аргумент, "продолжение", которое будет вызываться вместо рекурсии. Теперь определим две функции более высокого порядка,call
и memo
которые работают с функциями, имеющими аргумент продолжения. Первая функция, call
определено как:
let call f =
let rec g x =
f g x
in
g
он строит функция g
что не делает ничего особенного, но называет f
. Вторая функция memo
создает функцию g
реализация memoization:
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)
# 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>
вы должны прочитать раздел здесь: https://realworldocaml.org/v1/en/html/imperative-programming-1.html#memoization-and-dynamic-programming в книге Реальном Мире Вида OCaml.
это поможет вам действительно понять, как memo
работает.