first commit

This commit is contained in:
pena 2019-05-07 18:57:32 +02:00
commit f8f05a731b
15 changed files with 423 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
_build/
_coverage/
*.merlin
*.install

43
.ocamlformat Normal file
View file

@ -0,0 +1,43 @@
version=0.26.2
assignment-operator=end-line
break-cases=fit
break-fun-decl=wrap
break-fun-sig=wrap
break-infix=wrap
break-infix-before-func=false
break-separators=before
break-sequences=true
cases-exp-indent=2
cases-matching-exp-indent=normal
doc-comments=before
doc-comments-padding=2
doc-comments-tag-only=default
dock-collection-brackets=false
exp-grouping=preserve
field-space=loose
if-then-else=compact
indicate-multiline-delimiters=space
indicate-nested-or-patterns=unsafe-no
infix-precedence=indent
leading-nested-match-parens=false
let-and=sparse
let-binding-spacing=compact
let-module=compact
margin=80
max-indent=2
module-item-spacing=sparse
ocaml-version=4.14.0
ocp-indent-compat=false
parens-ite=false
parens-tuple=always
parse-docstrings=true
sequence-blank-line=preserve-one
sequence-style=terminator
single-case=compact
space-around-arrays=true
space-around-lists=true
space-around-records=true
space-around-variants=true
type-decl=sparse
wrap-comments=false
wrap-fun-args=true

8
CHANGES.md Normal file
View file

@ -0,0 +1,8 @@
## 0.2 - 2024-10-22
- add inlined hint
- update cache API to be compatible with latest OCaml
## 0.0.1 - 2019-11-21
First release

8
LICENSE.md Normal file
View file

@ -0,0 +1,8 @@
The ISC License (ISC)
=====================
Copyright © 2019-2020, pena
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

12
README.md Normal file
View file

@ -0,0 +1,12 @@
# memo
memo is an [OCaml] library for [memoïzation].
- [LICENSE]
- [CHANGELOG]
[CHANGELOG]: ./CHANGELOG.md
[LICENSE]: ./LICENSE.md
[memoïzation]: https://en.wikipedia.org/wiki/Memoization
[OCaml]: https://en.wikipedia.org/wiki/OCaml

3
doc/dune Normal file
View file

@ -0,0 +1,3 @@
(documentation
(package memo)
(mld_files usage index))

7
doc/index.mld Normal file
View file

@ -0,0 +1,7 @@
{0 memo}
[memo] is an {{:https://en.wikipedia.org/wiki/OCaml}OCaml} library for {{:https://en.wikipedia.org/wiki/Memoization}memoïzation}.
It provides easy ways to memoïze a function, in a type-safe and modular way and the ability to get forgetful memoïzation.
See {{:usage.html} usage} for a short explanation on how to use the library and {{:Memo/index.html} Memo} for a more detailled explanation.

117
doc/usage.mld Normal file
View file

@ -0,0 +1,117 @@
{0 Usage}
If you had a function [fibo] defined like this:
{[
let rec fibo x =
if x < 0 then invalid_arg "fibo";
if x < 2 then x
else fibo (x - 1) + fibo (x - 2)
]}
There's many different ways to memoïze it.
{1:simple_memo Simple memoïzation}
The easiest one is to rewrite it like this:
{[
let fibo = Memo.memo (fun fibo x ->
if x < 0 then invalid_arg "fibo";
if x < 2 then x
else fibo (x - 1) + fibo (x - 2))
]}
It'll use the {!module:Hashtbl} module from {!module:Stdlib} directly.
I'd like to thank {{:https://www.lri.fr/~conchon/}Sylvain Conchon} who taught me memoïzation and how to write this [memo] function when I was his student.
{1:custom_memo Using your own type, [equal] and [hash] functions}
We provide a {!module:Memo.Make} functor. It can be useful in case you don't want to use polymorphic equality or you are doing things like {{:https://en.wikipedia.org/wiki/Hash_consing}hash consing} and you know how to compare or hash your type more efficiently.
{[
let module Mem = Memo.Make(struct
type t = int
let equal = (=)
let hash = Hashtbl.hash
end)
let fibo = Mem.memo (fun fibo x ->
if x < 0 then invalid_arg "fibo";
if x < 2 then x
else fibo (x - 1) + fibo (x - 2))
]}
{1:forgetful_memo Forgetful memoïzation}
We provide a {!module:Memo.MakeWeak} functor. It works like the previous one, but the bindings in the memoïzation cache will be weak, allowing the garbage collector to remove them if they are not used somewhere else.
{[
let module Mem = Memo.MakeWeak(struct
type t = int
let equal = (=)
let hash = Hashtbl.hash
end)
let fibo = Mem.memo (fun fibo x ->
if x < 0 then invalid_arg "fibo";
if x < 2 then x
else fibo (x - 1) + fibo (x - 2))
]}
I'd like to thank {{:https://www.lri.fr/~filliatr/}Jean-Christophe Filliâtre} who taugh me forgetful memoïzation when I was doing research on {{:https://en.wikipedia.org/wiki/Binary_decision_diagram}binary decision diagram} under his direction while I was a first year master student.
{1:fake_memo Fake memoïzation}
We provide a {!module:Memo.Fake} functor. It is useful if you want to quickly test a function you memoïzed with our {!module:Memo.Make} or {!module:Memo.MakeWeak} functor, but without memoïzing it. It'll basically do nothing and should be equivalent to your initial non-memoïzed function.
{[
let module Mem = Memo.Fake(struct
type t = int
let equal = (=)
let hash = Hashtbl.hash
end)
let fibo = Mem.memo (fun fibo x ->
if x < 0 then invalid_arg "fibo";
if x < 2 then x
else fibo (x - 1) + fibo (x - 2))
]}
{1:custom_cache Using your own defined cache}
With the {!module:Memo.Mk} functor, you can also directly provide a [Cache] module, which should have the signature {!module-type:Hashtbl.S}. We will include your cache module and use it to define a [memo] function:
{[
let module Mem = Memo.Mk(
Hashtbl.Make(struct
type t = int
let equal = (=)
let hash = Hashtbl.hash
end)
end)
let fibo = Mem.memo (fun fibo x ->
if x < 0 then invalid_arg "fibo";
if x < 2 then x
else fibo (x - 1) + fibo (x - 2))
]}
This example is useless and equivalent to using the {!module:Memo.Make} functor directly.
If you find a real use case for this which doesn't need new dependencies, contact me and I'll be happy to add a new functor to the library.
It should be useful only if you want to use another {!module:Hashtbl} implementation or things like this.
{1:tuning Tuning}
There's a default value for the initial cache size. You can set it to the value of your choice, reset it to the default and get the current value like this:
{[
Memo.set_initial_cache_size 1024;
Memo.reset_initial_cache_size ();
let curr_size = Memo.get_initial_cache_size ()
]}
Note that with the current implementation of hash tables in OCaml, it's better if you choose a power of two. You may saw some code using a prime number, it's because some years ago it was the best thing to do as the hash tables implementation was different. {{:https://www.lri.fr/~filliatr/}Jean-Christophe Filliâtre} explained this to me, thanks again ! Also keep in mind that if you use your own defined cache thanks to the {!module:Memo.Mk} functor, it may not be the right thing to do.

27
dune-project Normal file
View file

@ -0,0 +1,27 @@
(lang dune 3.0)
(name memo)
(license ISC)
(authors "pena <pena@kumikode.org>")
(maintainers "pena <pena@kumikode.org>")
(source
(uri git://forge.kumikode.org/kumikode/memo.git))
(bug_reports https://forge.kumikode.org/kumikode/memo/issues)
(homepage https://forge.kumikode.org/kumikode/memo)
(generate_opam_files true)
(package
(name memo)
(synopsis "Memoïzation library")
(description
"memo is an OCaml library for memoïzation. It provides easy ways to memoïze a function, in a type-safe and modular way and the ability to get forgetful memoïzation.")
(depends
(ocaml
(>= 4.12))))

30
memo.opam Normal file
View file

@ -0,0 +1,30 @@
# This file is generated by dune, edit dune-project instead
opam-version: "2.0"
synopsis: "Memoïzation library"
description:
"memo is an OCaml library for memoïzation. It provides easy ways to memoïze a function, in a type-safe and modular way and the ability to get forgetful memoïzation."
maintainer: ["pena <pena@kumikode.org>"]
authors: ["pena <pena@kumikode.org>"]
license: "ISC"
homepage: "https://forge.kumikode.org/kumikode/memo"
bug-reports: "https://forge.kumikode.org/kumikode/memo/issues"
depends: [
"dune" {>= "3.0"}
"ocaml" {>= "4.12"}
"odoc" {with-doc}
]
build: [
["dune" "subst"] {dev}
[
"dune"
"build"
"-p"
name
"-j"
jobs
"@install"
"@runtest" {with-test}
"@doc" {with-doc}
]
]
dev-repo: "git://forge.kumikode.org/kumikode/memo.git"

14
shell.nix Normal file
View file

@ -0,0 +1,14 @@
{ pkgs ? import <nixpkgs> { } }:
pkgs.mkShell {
nativeBuildInputs = with pkgs.ocamlPackages; [
dune_3
findlib
merlin
ocaml
ocamlformat
odoc
];
buildInputs = with pkgs.ocamlPackages; [
];
}

3
src/dune Normal file
View file

@ -0,0 +1,3 @@
(library
(public_name memo)
(wrapped false))

106
src/memo.ml Normal file
View file

@ -0,0 +1,106 @@
(** *)
(** {1:tuning Tuning} *)
(**/**)
let get_initial_cache_size, set_initial_cache_size, reset_initial_cache_size =
let default = 512 in
let initial_cache_size = ref default in
( (fun () -> !initial_cache_size)
, (fun size -> initial_cache_size := size)
, fun () -> initial_cache_size := default )
(**/**)
(** [mk_memo create find add ff] gives a memoïzed version of the functional [ff]
using the functions [create], [find] and [add] for the cache. It's used
internally and you shouldn't have to use it. *)
let mk_memo create find add ff =
let cache = create (get_initial_cache_size ()) in
let rec f k =
try find cache k
with Not_found ->
let v = ff f k in
add cache k v;
v
in
f
(**/**)
(** {1:generic Generic interface} *)
(** [memo ff] gives you a memoïzed version of the [ff] functional. *)
let memo ff =
let open Hashtbl in
mk_memo create find add ff
(** {1:functors Functorial interface} *)
(** The output signature of the functors {!module:Mk}, {!module:Make},
{!module:MakeWeak} and {!module:Fake}.*)
module type S = sig
type t
val memo : ((t -> 'a) -> t -> 'a) -> t -> 'a
end
(** The type of custom cache modules. *)
module type Cache = sig
(** The type of keys. *)
type key
(** The type of caches with ['a] values. *)
type !'a t
(** [create n] creates a new, empty cache, with initial size [n]. For best
results, [n] should be on the order of the expected number of elements
that will be in the cache. The cache must grow as needed, so [n] is just
an initial guess. *)
val create : int -> 'a t
(** Empty a cache. *)
val clear : 'a t -> unit
(** [add cache key v] adds a binding of [key] to [v] in cache [cache]. *)
val add : 'a t -> key -> 'a -> unit
(** [find cache v] returns the current binding of [v] in [cache], or must
raise [Not_found] if [v] is not in [cache]. *)
val find : 'a t -> key -> 'a
end
(** With the {!module:Mk} functor, you can also directly provide a [Cache]
module, which should have the signature [Cache]. We will include your cache
module and use it to define a [memo] function. It should be useful only if
you want to use another [Hashtbl] implementation or things like this. *)
module Mk (Cache : Cache) = struct
include Cache
let memo ff = mk_memo Cache.create Cache.find Cache.add ff
end
(** Functor that can be useful in case you don't want to use polymorphic
equality or you are doing things like hashconsing and you know how to
compare or hash your type more efficiently. *)
module Make (H : Hashtbl.HashedType) =
Mk [@inlined hint] (Hashtbl.Make [@inlined hint] (H))
(** Functor that works like the [Make] one, but the bindings in the memoïzation
cache will be weak, allowing the garbage collector to remove them if they
are not used somewhere else. *)
module MakeWeak (H : Hashtbl.HashedType) =
Mk [@inlined hint] (Ephemeron.K1.Make [@inlined hint] (H))
(** Functor that is useful if you want to quickly test a function you memoïzed
with our {!module:Make} or {!module:MakeWeak} functor, but without memoïzing
it. It'll basically do nothing and should be equivalent to your initial
non-memoïzed function. *)
module Fake (H : Hashtbl.HashedType) = Mk [@inlined hint] (struct
include Hashtbl.Make (H)
let find _ _ = raise_notrace Not_found
let add _ _ _ = ()
end)

3
test/dune Normal file
View file

@ -0,0 +1,3 @@
(test
(name test)
(libraries memo))

38
test/test.ml Normal file
View file

@ -0,0 +1,38 @@
let _ =
let fibo_res = [| 0; 1; 1; 2; 3; 5; 8; 13; 21; 34; 55; 89 |] in
let fibo fibo x =
if x < 0 then invalid_arg "fibo";
if x < 2 then x else fibo (x - 1) + fibo (x - 2)
in
let fibo1 = Memo.memo fibo in
let module Mem = Memo.Make (struct
type t = int
let equal = ( = )
let hash = Hashtbl.hash
end) in
let fibo2 = Mem.memo fibo in
let module MemWeak = Memo.MakeWeak (struct
type t = int
let equal = ( = )
let hash = Hashtbl.hash
end) in
let fibo3 = MemWeak.memo fibo in
let module MemFake = Memo.Fake (struct
type t = int
let equal = ( = )
let hash = Hashtbl.hash
end) in
let fibo4 = MemFake.memo fibo in
for i = 0 to Array.length fibo_res - 1 do
assert (fibo1 i = fibo_res.(i));
assert (fibo2 i = fibo_res.(i));
assert (fibo3 i = fibo_res.(i));
assert (fibo4 i = fibo_res.(i))
done;
Format.printf "Tests are OK !@."