Welcome! Please see the About page for a little more info on how this works.

0 votes
in core.memoize by

Use case: I have this code that works great

(require '[clojure.core.cache :as cache])
(require '[clojure.core.memoize :as memo])
(import 'clojure.core.memoize.PluggableMemoization)

(defn lru-ttl-cache [base threshold ttl]
  (-> base
      (cache/lru-cache-factory :threshold threshold)
      (cache/ttl-cache-factory :ttl ttl)))

(defn lru-ttl-memo
  ([f base threshold ttl]
      #(PluggableMemoization. %1 (lru-ttl-cache %4 %2 %3))
      (@#'memo/derefable-seed base))))

but it depends on a private var. This should really be public, so clients can more easily provide their own implementations.

Fantastic libraries, by the way. I love working in the Clojure ecosystem.

1 Answer

+1 vote
edited by
Best answer

JIRA issue created: https://clojure.atlassian.net/browse/CMEMOIZE-28

There may be a better way to provide for this sort of extension since the fact that the cache elements are derefable is meant to be an implementation detail.

That's probably a good idea. I wrote a helper function to abstract away the details:

   (defn build-helper [cache-factory f base & args]
      #(PluggableMemoization. %1 (apply cache-factory %2 %3))
      (derefable-seed base)

The memo implementation then becomes (sans docs)

   (defn memo
     ([f] (memo f {}))
     ([f seed]
      (build-helper cache/basic-cache-factory f seed)))

and my earlier lru-ttl-memo function simplifies to

   (def lru-ttl-memo
     (partial memo/build-helper lru-ttl-cache))

Definitely a huge gain in simplicity, and agnostic of implementation details.

Diff at https://github.com/john-shaffer/core.memoize/commit/669febe3af7f325de43cab82ab7d09469fd148a1

I really have no idea how you run the tests normally. I had to symlink the code into a leiningen project, hot-patch it, and call clojure.test/run-tests.
Contrib projects are generally run with Maven: mvn test

All of the Contrib projects I maintain generally also support the CLI/deps.edn and you can run those tests with: clojure -A:test:runner

Optionally providing an alias for the version of Clojure to use: clojure -A:test:runner:1.8

The reason for the build-memoizer being so generic right now is that it needs to be able to support arbitrary caches which can have arbitrary arguments in an arbitrary order (so your helper will only work for some caches).
While looking at this, I realized there is a bug in PluggableMemoization's seed function https://clojure.atlassian.net/browse/CMEMOIZE-27
The master branch of clojure.core.memoize has a fix for this: rather than making derefable-seed public, I've added a new memoizer function that takes a function, a cache (not a cache factory) and an optional seed/base hash map. This makes it easier to create custom cached functions while exposing less of the internals of the library.

I'll cut a new release "soon" with this updated functionality.
Thank you. I'm using this now at https://github.com/john-shaffer/krulak/blob/ba09b5098eff2f614ffe2b98c6ed5c6fb2783dc5/src/krulak.clj#L34
I also created several other functions that reach into memoize internals. I basically needed memoize equivalents of everything in the core.cache interface. Is anything like that on the table for adding to core.memoize?
I'm not sure what you're asking here -- core.memoize is based on a PluggableMemoization type that implements exactly the core.cache protocol, so all the same functionality is present. Can you articular what you feel needs adding to core.memoize? It makes me uncomfortable that you feel the private parts of core.memoize need to be used in order to  extend and customize memoization. Feel free to start new question thread for each part of the library you feel needs addressing.
I guess I'm not clear on what is the supported way to swap in the PluggableMemoization. I can access the :clojure.core.memoize/cache atom, but cache-id's being private gave me the impression that the location of the atom is an implementation detail that might change. Is it okay to rely on that atom, or am I missing something else?
Do you mean things like this: https://github.com/clojure/core.memoize/blob/master/src/test/clojure/clojure/core/memoize_test.clj#L207-L217 using memo-swap! (combined with snapshot to access what is currently in the cache)?

If not, can you explain the problems you are trying to solve in a way that I can see what you think is missing in the API?
memo-swap! plus snapshot works, but snapshot creates a new map with every item in the cache, which is too much overhead for the case of setting a specific value in the cache. I'll make a new thread for that. My other uses fit nicely into the API once I understood the API better. I do want the ability to swap in a cache with different parameters, and snapshot is perfect for that.