Browse Source

write better doc

master
zapashcanon 2 years ago
parent
commit
be02e3b1bf
Signed by: zapashcanon GPG Key ID: 8981C3C62D1D28F1
  1. 2
      LICENSE.md
  2. 138
      README.md
  3. 3
      doc/dune
  4. 7
      doc/index.mld
  5. 117
      doc/usage.mld
  6. 79
      src/memo.ml

2
LICENSE.md

@ -1,7 +1,7 @@
The ISC License (ISC)
=====================
Copyright © 2019, Léo Andrès
Copyright © 2019-2020, Léo Andrès
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.

138
README.md

@ -1,140 +1,14 @@
# Memo [![builds.sr.ht status](https://builds.sr.ht/~zapashcanon/memo.svg)](https://builds.sr.ht/~zapashcanon/memo?)
# memo [![builds.sr.ht status](https://builds.sr.ht/~zapashcanon/memo.svg)](https://builds.sr.ht/~zapashcanon/memo?)
Memo is an [OCaml] library for [memoïzation].
memo is an [OCaml] library for [memoïzation].
## Usage
If you had a function `fibo` defined like this:
```ocaml
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.
### Simple memoïzation
The easiest one is to rewrite it like this:
```ocaml
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 `Hashtbl` module directly.
I'd like to thank [Sylvain Conchon] who taught me memoïzation and how to write this `memo` function when I was his student.
### Using you own type, `equal` and `hash` functions
We provide a `Make` functor. It can be useful in case you don't want to use polymorphic equality or you are doing things like [hash consing] and you know how to compare or hash your type more efficiently.
```ocaml
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))
```
### Forgetful memoïzation
We provide a `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.
```ocaml
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 [Jean-Christophe Filliâtre] who taugh me forgetful memoïzation when I was doing research on [binary decision diagram] under his direction while I was a first year master student.
### Fake memoïzation
We provide a `Fake` functor. It is useful if you want to quickly test a function you memoïzed with our `Make` or `MakeWeak` functor, but without memoïzing it. It'll basically do nothing and should be equivalent to your initial non-memoïzed function.
```ocaml
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))
```
### Using your own defined cache
With the `Mk` functor, you can also directly provide a `Cache` module, which should have the signature `Hashtbl.S`. We will include your cache module and use it to define a `memo` function:
```ocaml
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 `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 `Hashtbl` implementation or things like this.
### 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:
```ocaml
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. [Jean-Christophe Filliâtre] explained this to me, thanks again ! Also keep in mind that if you use your own defined cache using the `Mk` functor, it may not be the right thing to do.
## License
See [LICENSE].
## Changelog
See [CHANGELOG].
- [LICENSE]
- [CHANGELOG]
- [documentation]
[CHANGELOG]: ./CHANGELOG.md
[documentation]: https://doc.zapashcanon.fr/memo/
[LICENSE]: ./LICENSE.md
[binary decision diagram]: https://en.wikipedia.org/wiki/Binary_decision_diagram
[Jean-Christophe Filliâtre]: https://www.lri.fr/~filliatr/
[hash consing]: https://en.wikipedia.org/wiki/Hash_consing
[memoïzation]: https://en.wikipedia.org/wiki/Memoization
[OCaml]: https://en.wikipedia.org/wiki/OCaml
[Sylvain Conchon]: https://www.lri.fr/~conchon/

3
doc/dune

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

7
doc/index.mld

@ -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

@ -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 you 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.

79
src/memo.ml

@ -1,22 +1,30 @@
(** The output signature of the functors [Mk], [Make], [MakeWeak] and [Fake].*)
module type S = sig
type t
(** *)
val memo : ((t -> 'a) -> t -> 'a) -> t -> 'a
end
(** {1:tuning Tuning} *)
(**/**)
(** Default for the initial size when creating a cache. *)
let default_cache_size = 512
(** Current value for the initial cache size. *)
let initial_cache_size = ref default_cache_size
(**/**)
(** Get the value used as an initial size when creating a cache. *)
let get_initial_cache_size () = !initial_cache_size
(** Set the value used as an initial size when creating a cache. *)
let set_initial_cache_size size = initial_cache_size := size
(** Functions on the initial size used when creating a cache, you can change get
the current value, update it to a number of your choice, or reset it to the
default value. *)
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 )
(** Reset to default the value used as an initial size when creating a cache. *)
let reset_initial_cache_size () = initial_cache_size := default_cache_size
(**/**)
(** [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
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
@ -28,8 +36,26 @@ let mk_memo create find add ff =
in
f
(** With the [Mk] functor, you can also directly provide a [Cache] module, which
should have the signature [Hashtbl.S]. We will include your cache module and
(**/**)
(** {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
(** With the {!module:Mk} functor, you can also directly provide a [Cache] module, which
should have the signature [Hashtbl.S]. 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 : Hashtbl.S) = struct
@ -38,18 +64,18 @@ module Mk (Cache : Hashtbl.S) = struct
let memo ff = mk_memo Cache.create Cache.find Cache.add ff
end
(** 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 (Ephemeron.K1.Make (H))
(** 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
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 (Hashtbl.Make (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 (Ephemeron.K1.Make (H))
(** Functor that is useful if you want to quickly test a function you memoïzed
with our [Make] or [MakeWeak] functor, but without memoïzing it. It'll
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 (struct
@ -59,8 +85,3 @@ module Fake (H : Hashtbl.HashedType) = Mk (struct
let add _ _ _ = ()
end)
(** [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
Loading…
Cancel
Save