123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116open!ImportopenStd_internaltype('a,'b)fn='a->'bmoduleResult=structtype'at=|Rvalof'a|Exptofexnletreturn=function|Rvalv->v|Expte->raisee;;letcapturefx=tryRval(fx)with|e->Expte;;endletunitf=letl=Lazy.from_funfinfun()->Lazy.forcel;;letunbounded(typea)?(hashable=Hashtbl.Hashable.poly)f=letcache=letmoduleA=Hashable.Make_plain_and_derive_hash_fold_t(structtypet=alet{Hashtbl.Hashable.hash;compare;sexp_of_t}=hashableend)inA.Table.create()~size:0in(* Allocate this closure at the call to [unbounded], not at each call to the memoized
function. *)letreally_call_farg=Result.capturefarginfunarg->Result.return(Hashtbl.findi_or_addcachearg~default:really_call_f);;(* the same but with a bound on cache size *)letlru(typea)?(hashable=Hashtbl.Hashable.poly)~max_cache_sizef=ifmax_cache_size<=0thenfailwithf"Memo.lru: max_cache_size of %i <= 0"max_cache_size();letmoduleCache=Hash_queue.Make(structtypet=alet{Hashtbl.Hashable.hash;compare;sexp_of_t}=hashableend)inletcache=Cache.create()infunarg->Result.return(matchCache.lookup_and_move_to_backcacheargwith|Someresult->result|None->letresult=Result.capturefarginCache.enqueue_back_exncacheargresult;(* eject least recently used cache entry *)ifCache.lengthcache>max_cache_sizethenignore(Cache.dequeue_front_exncache:_Result.t);result);;letgeneral?hashable?cache_size_boundf=matchcache_size_boundwith|None->unbounded?hashablef|Somen->lru?hashable~max_cache_size:nf;;(* We expect [f_onestep] to be a one-step unrolled recursive function; see the mli. Hence,
here we create the memoized function _and_ pass it to [f_onestep] to be used for
recursive calls.
Note that we immediately apply [f_onestep] to its first argument here so that any
precomputation is performed when the user calls [recursive].
As an example, if someone writes this non-memoized code:
[ let rec f = let data = compute_without_using_f () in fun x -> ... f ... ]
and converts to memoization by doing:
{[
let f =
let f_onestep f = let data = compute_without_using_f () in fun x -> ... f ... in
recursive f_onestep
]}
we want to compute [data] immediately. If we had [fun x -> f_onestep (force memoized)
x] below, we'd recompute [data] each time the user calls [f] on an argument that hadn't
yet been memoized. *)letrecursive~hashable?cache_size_boundf_onestep=letrecmemoized=lazy(general~hashable?cache_size_bound(f_onestep(funx->(forcememoized)x)))inforcememoized;;letof_comparable(typeindex)(moduleM:Comparable.S_plainwithtypet=index)f=letm=refM.Map.emptyinfun(x:M.t)->letv=matchMap.find!mxwith|Somev->v|None->letv=Result.capturefxinm:=Map.set!m~key:x~data:v;vinResult.returnv;;