//// The cache is a [Erlang Term Storage database](https://www.erlang.org/doc/apps/erts/persistent_term.html). import gleam/string import internal/carpenter/table.{AutoWriteConcurrency, Private} import tempo/instant pub type Cache(k, v) = table.Set(k, v) /// Make a new memoization cache that will be used for the rest of the inner scope /// of the provided function. /// Pass this cache to the function you want to memoize. /// /// This is best used with a `use` expression: /// ```gleam /// use cache <- create() /// f(a, b, c, cache) /// ``` /// pub fn create(apply fun: fn(Cache(k, v)) -> t) { let table_name = instant.now() |> string.inspect let assert Ok(cache_table) = table.build(table_name) |> table.privacy(Private) |> table.write_concurrency(AutoWriteConcurrency) |> table.read_concurrency(True) |> table.decentralized_counters(True) |> table.compression(False) |> table.set() let result = fun(cache_table) table.drop(cache_table) result } /// Manually add a key-value pair to the memoization cache. /// Useful if you need to pre-seed the cache with a starting value, for example. pub fn set(in cache: Cache(k, v), for key: k, insert value: v) -> Nil { table.insert(cache, [#(key, value)]) } /// Manually look up a value from the memoization cache for a given key. /// Useful if you want to also return intermediate results as well as a final result, for example. pub fn get(from cache: Cache(k, v), fetch key: k) -> Result(v, Nil) { case table.lookup(cache, key) { [] -> Error(Nil) [#(_, v), ..] -> Ok(v) } } /// Look up the value associated with the given key in the memoization cache, /// and return it if it exists. If it doesn't exist, evaluate the callback function /// update the cache with the key and the corresponding value the callback returned, /// and return that value. /// /// This works well with a `use` expression: /// ```gleam /// fn f(a, b, c, cache) { /// use <- memoize(cache, #(a, b, c)) /// // function body goes here /// } /// ``` /// pub fn memoize(with cache: Cache(k, v), this key: k, apply fun: fn() -> v) -> v { case get(from: cache, fetch: key) { Ok(value) -> value Error(Nil) -> { let result = fun() set(in: cache, for: key, insert: result) result } } }