1

Background

I'm trying to write the following function:

  (defn cache-get [dynamic-var k func]
    (if-let [v (get-in dynamic-var k)]
      v
      (let [ans (func)]
        (set! dynamic-var (assoc-in dvar k ans))
        ans)))

The arguments to cache-get are:

  • dynamic-var, something like (def ^:dynamic *my-local-cache*)
  • k, a key, something like :foo
  • func, a function, to be called if the value does not exist

The behavior of the function is:

  • if the key k exists, just return the value
  • if the key k does not exist, (1) compute it by calling (func) and (2) store it in the cache

Problem I am facing

The problem I am facing is that I apparently can not do (set! dynamic-var ...), because the function has no idea I will be passing it a dynamically bound var and thinks dynamic-var is a regular variable.

My current option:

Rewrite the function as a macro -- which is fine, but I prefer to not use macros unless absolutely necessary.

My question: is there a way to implement cache-get without using macros?

[Also, I want this to be a dynamic-var, not an atom]

Thanks!

Edit 1

I try suggestion of var-set. I get the following error:

(def ^:dynamic *query-ctx* {})

(defn cache-get [dynamic-var k func]
  (if-let [v (get-in dynamic-var k)]
    v
    (let [ans (func)]
      (var-set dynamic-var (assoc-in dynamic-var k ans))
      ans)))

(cache-get *query-ctx* [:foo-key] (fn [] :foo-value))

;; clojure.lang.PersistentArrayMap cannot be cast to clojure.lang.Var
Nathan Davis
  • 5,636
  • 27
  • 39
eav db
  • 595
  • 3
  • 13
  • It's not clear how you intend to use this. Please include a sample of what you want to do, including expected output. Since we are dealing with dynamic vars, it is particularly relevant to know how you expect the code to work in a multi-threaded environment. Until these details are spelled out for us, we are just guessing at what you are trying to do. – Nathan Davis Nov 09 '16 at 20:28
  • @NathanDavis: 1) thanks for edits for fixing up the code, 2) I haven't actually thought about the multi threading at all, 3) the "edit 1" shows an attempt at using it -- i.e. the last call of (cache-get *query-ctx* .... ) is a "get this if key exists, otherwise, call func to generate it" – eav db Nov 09 '16 at 20:42

1 Answers1

1

To answer the question: you want var-set instead of set!.

Quick question: if you're passing in the variable, do you really need a dynamic var?

[Also, I want this to be a dynamic-var, not an atom]

Can you expand on that? It does seem like an atom would be a much more logical choice. Do you need a thread-specific version of that cache?

Update: example call:

(cache-get #'*query-ctx* [:foo-key] (fn [] :foo-value))

Joost Diepenmaat
  • 17,633
  • 3
  • 44
  • 53
  • Thanks for prompt response! I tried var-set. I get error of ;; clojure.lang.PersistentArrayMap cannot be cast to clojure.lang.Var -- can you please let me know how to fix this? Thanks! – eav db Nov 09 '16 at 08:26
  • You need to pass in the var itself instead of its value. So something like `(cache-get #'*query-ctx [:foo-key] (fn [] :foo-value))` should work. Where #'foo is reader syntax for `(var foo)` – Joost Diepenmaat Nov 09 '16 at 08:50
  • You may also find this question helpful: http://stackoverflow.com/questions/39550513/when-to-use-a-var-instead-of-a-function/39550785 – Alan Thompson Nov 09 '16 at 16:46