Как запомнить рекурсивные функции?
Рассмотрим рекурсивную функцию, скажем алгоритм Евклида, определяемый:
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
, реализующую запоминание:Эти функции имеют следующие сигнатуры.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
, первую без запоминания и вторую с запоминанием: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
.