You can fire an event at a memoized function. The target can be a particular function (or MountPoint), or you can specify a tag (and all tagged functions get the event). Each function can configure its own handler for events. Event can be any object, I suggest you use a structure that will enable event handlers to distinguish events.
Event handler is a function of two arguments, the MountPoint it's been triggered (most core functions work on those) and the event.
Main use case is to enable adding entries to different functions from same data. Example:
(defn get-project-name
"Returns project name"
[project-id])
(m/memo #'get-project-name inf)
(defn get-project-owner
"Returns project's owner user ID"
[project-id])
(m/memo #'get-project-owner inf)
(defn get-user-projects
"Returns a big expensive list"
[user-id]
(let [project-list '...]
project-list))
In that example, when get-user-projects
is called, we might load over a 100 projects, and we'd hate to waste that
and not inform get-project-name
and get-project-owner
about the facts we've established here, especially since we
might be calling these smaller functions in a loop right after fetching the big list.
Here's a way to make sure data is reused by manually pushing entries into the caches as supported by most caching libs:
(defn get-user-projects
"Returns a big expensive list"
[user-id]
(let [project-list '...]
;; preload entries for seen projects into caches
(m/memo-add! get-project-name
(zipmap (map (comp list :id) project-list)
(map :name project-list)))
(m/memo-add! get-project-owner
(zipmap (map (comp list :id) project-list)
(repeat user-id)))
project-list))
The problem with this solution is that it is an absolute nightmare to maintain:
get-project-name
means that I have to also fix producing
functions like get-user-projects
get-project-owner
I must fix the get-user-projects
code that pushes cache entriesget-user-projects
then each such producer must implement all these changes
and each has a massive block to feed all the consumersI can use events instead and co-locate the code that feeds the cache with the function:
(defn get-project-name
"Returns project name"
[project-id])
(m/memo #'get-project-name
(assoc inf
mc/evt-fn (m/evt-cache-add
:project-seen
(fn [{:keys [name id]}] {[id] name}))
mc/tags [:project]))
(defn get-project-owner
"Returns project's owner user ID"
[project-id])
(m/memo #'get-project-owner
(assoc inf
mc/evt-fn (m/evt-cache-add
:project-seen
(fn [{:keys [id user-id]}] {[id] user-id}))
mc/tags [:project]))
(defn get-user-projects
"Returns a big expensive list"
[user-id]
(let [project-list '...]
(doseq [p project-list]
(m/fire-event! :project [:project-seen (assoc p :user-id user-id)]))
project-list))
We're using the evt-cache-add
convenience function that assumes event shape is a
vector of type + payload and that the intent is to add entries to the cache.
In this case the producer function is only concerned with firing events at tagged caches. It doesn't need to consider the number of shape of consumers.
The caching declaration of consumer functions is where there the cache feeding logic is located, which makes things manageable.
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close