Memoization for functions that use map destructuring for Clojure and Clojurescript.
Maps provide us with unordered and associative data.
It's arguably the only data structure that we need to write
function parameters. They compose terrifically and are extensible (using merge
and namespaced keys) Ordered parameters are (often) just unjustified added complexity:
(defn wrong [a b])
(defn right [{:keys [a b]}])
Clojure's memoize
only supports memoizing on the entire map being passed instead of just the keys that are being used.
Memokey fixes this. Now you can use the power of maps combined with the performance benefits of memoization.
Add to deps.edn:
{org.fversnel/memokey {:git/url "https://github.com/fversnel/memokey"
:sha "bae1e62955ae19410fb92bdd996e6f8e4a340d3d"}}
Require the namespace:
(:require [org.fversnel.memokey :as m])
Then write a function using the memo-fn
macro:
(def example-fn
(m/memo-fn
;; map destructuring argument
{:a/keys [b]}
;; function body
(println "executing slow function")
(Thread/sleep 5000)
(identity b)))
When we call the memoized function it will only look at the elements
of the map that are actually being destructured.
When we call the function twice with the same value
for :a/b
regardless of the value of :b/c
we get the memoized
result back:
(example-fn {:a/b 42 :b/c 43}) ;; output: "executing slow function"; 42
(example-fn {:a/b 42 :b/c 44}) ;; output: 42
Optionally we can provide a directive to memoize on a subset of the bindings:
(m/memo-fn
{:a/keys [b c]
;; Memoizes only on binding c
:org.fversnel.memokey/memoize-bindings [c]}
(println "executing slow function")
(Thread/sleep 5000)
[b c])
And provide a custom implementation of the org.fversnel.memokey.Cache protocol:
(by default memokey uses an (atom {})
as its cache)
(m/memo-fn
{:a/keys [b]
;; Custom cache:
:org.fversnel.memokey/cache (create-custom-cache)}
(println "executing slow function")
(Thread/sleep 5000)
(identity b))
It works on all kinds of map destructuring:
{;; regular non-namespaced keys
:keys [a]
;; namespaced keys
:keys [:b/c]
;; namespaced :a/keys
:a/keys [b]
;; regular destructuring
e :d/e
;; string based destructuring
:strs [some-string]
;; symbol based destructuring
:syms [some-symbol]}
Each memoized function contains the following meta data:
(m/memo-fn {:keys [a]} a)
=> #:org.fversnel.memokey{:memoize-bindings [a],
:cache #object[org.fversnel.memokey$atom_cache$reify__470 0x144ab54 "org.fversnel.memokey$atom_cache$reify__470@144ab54"]}
To turn off memoization all together (without any overhead):
(m/memo-fn {:keys [a] :org.fversnel.memokey/memoize? false} a)
- Provide different types of caches (supporting
clojure.core.cache
)