Skip to content

Commit

Permalink
v2.0.0: ETS implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
hunkyjimpjorps committed Apr 19, 2024
1 parent 0eb89a1 commit 19b1dd9
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 5 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Changelog

## v2.0.0 - 24-04-18

* Added the [Erlang Term Storage](https://www.erlang.org/doc/man/ets.html) implementation as `rememo/ets/memo`. This has reduced overhead compared to the original OTP implementation, which required message-passing to a `gleam_otp/actor` holding the memoization state.
* The original module was renamed from `rememo` to `rememo/otp/memo`.

## v1.0.0 -- 24-04-16

* Initial release.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,21 @@ There is some overhead to sending messages and caching the dictionary of results
gleam add rememo
```
```gleam
import rememo
import memo/ets/memo // This is the recommended implementation to use
import gleam/io
pub fn main() {
// Start the actor that holds the cached values
// for the duration of this block
use cache <- rememo.create()
use cache <- memo.create()
fib(300, cache)
|> io.debug
}
fn fib(n, cache) {
// Check if a value exists for the key n
// Use it if it exists, update the cache if it doesn't
use <- rememo.memoize(cache, n)
use <- memo.memoize(cache, n)
case n {
1 | 2 -> 1
n -> fib(n - 1, cache) + fib(n - 2, cache)
Expand Down
4 changes: 3 additions & 1 deletion gleam.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name = "rememo"
version = "1.0.0"
version = "2.0.0"

# Fill out these fields if you intend to generate HTML documentation or publish
# your project to the Hex package manager.
Expand All @@ -16,6 +16,8 @@ repository = { type = "github", user = "hunkyjimpjorps", repo = "rememo" }
gleam_stdlib = "~> 0.34 or ~> 1.0"
gleam_otp = "~> 0.10"
gleam_erlang = "~> 0.25"
carpenter = ">= 0.3.1 and < 1.0.0"
youid = ">= 1.0.0 and < 2.0.0"

[dev-dependencies]
gleeunit = "~> 1.0"
5 changes: 5 additions & 0 deletions manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@
# You typically do not need to edit this file

packages = [
{ name = "carpenter", version = "0.3.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "carpenter", source = "hex", outer_checksum = "7F5AF15A315CF32E8EDD0700BC1E6711618F8049AFE66DFCE82D1161B33F7F1B" },
{ name = "gleam_crypto", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_crypto", source = "hex", outer_checksum = "ADD058DEDE8F0341F1ADE3AAC492A224F15700829D9A3A3F9ADF370F875C51B7" },
{ name = "gleam_erlang", version = "0.25.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "054D571A7092D2A9727B3E5D183B7507DAB0DA41556EC9133606F09C15497373" },
{ name = "gleam_otp", version = "0.10.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "0B04FE915ACECE539B317F9652CAADBBC0F000184D586AAAF2D94C100945D72B" },
{ name = "gleam_stdlib", version = "0.36.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "C0D14D807FEC6F8A08A7C9EF8DFDE6AE5C10E40E21325B2B29365965D82EB3D4" },
{ name = "gleeunit", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "72CDC3D3F719478F26C4E2C5FED3E657AC81EC14A47D2D2DEBB8693CA3220C3B" },
{ name = "youid", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_crypto", "gleam_erlang", "gleam_stdlib"], otp_app = "youid", source = "hex", outer_checksum = "D0ECB8C57449866A1B1A83964EEEA72160BF8E05F8F2EEA540BD0F1626FA94E2" },
]

[requirements]
carpenter = { version = ">= 0.3.1 and < 1.0.0" }
gleam_erlang = { version = "~> 0.25" }
gleam_otp = { version = "~> 0.10" }
gleam_stdlib = { version = "~> 0.34 or ~> 1.0" }
gleeunit = { version = "~> 1.0" }
youid = { version = ">= 1.0.0 and < 2.0.0"}
66 changes: 66 additions & 0 deletions src/rememo/ets/memo.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//// This is the memoization implementation that uses [Erlang Term Storage](https://www.erlang.org/doc/man/ets.html) (ETS).
//// This is the faster (and newer) of the two implementations.

import carpenter/table.{type Set, AutoWriteConcurrency, Private}
import youid/uuid

/// Start an actor that holds a memoization cache. 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(Set(k, v)) -> t) {
let table_name = uuid.v4_string()

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.
pub fn set(in cache: Set(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.
pub fn get(from cache: Set(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
/// and update the cache with the value it returns.
///
/// 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: Set(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
}
}
}
5 changes: 4 additions & 1 deletion src/rememo.gleam → src/rememo/otp/memo.gleam
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
//// This is the memoization implementation that uses [`gleam/otp/actor`](https://www.erlang.org/doc/man/ets.html).
//// This is the slower (and original) of the two implementations.

import gleam/dict.{type Dict}
import gleam/otp/actor.{type Next, Continue, Stop}
import gleam/erlang/process.{type Subject, Normal}
import gleam/option.{None}
import gleam/otp/actor.{type Next, Continue, Stop}

const timeout = 1000

Expand Down

0 comments on commit 19b1dd9

Please sign in to comment.