Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to invalidate a value of a memoized function in Clojure?

Tags:

clojure

I have a function that makes an HTTP request for a token, which will be used in future requests. This token is valid for an unspecified amount of time, probably a few hours or so.

  (defn request-app-token
    "Request an app token from FB. Useful for app-global actions, such as creating test users."
    [client-credentials]
    (-> {"client_id" (:client-id client-credentials)
          "client_secret" (:client-secret client-credentials)
          "grant_type" "client_credentials"}
         (form-encode)
         ((partial str fb-graph-api "/oauth/access_token?"))
         (client/get {:throw-entire-message? true})
         :body
         (json/read-str)
         (get "access_token")))

To me, this looks like a job for memoize: keep a copy of the token and reuse it, instead of requesting new tokens every time I need one.

  (def get-app-token (memoize request-app-token)) ; So beautiful :D

I just need to handle the case where the token expires. For that, I will invert control; take a function that needs a token, attempt to run it with the memoized token, and if that fails, then try it again with a fresh token.

  (defn with-app-token [client-credentials f]
    (try (f (get-app-token client-credentials))
         (catch Exception e ; I know I should be more specific here, I only want to catch HTTP 400 responses
           (f (request-app-token client-credentials)))))

This would sort of work, but after the first token expires, then all subsequent calls to with-app-token would make a request for a new token. I need some way to clear or invalidate the memoized return value of get-app-token.

I could write my own memoize function, with an invalidate function that clears a specific result, but I am wondering if there is already something in the language that is meant to handle this?

like image 226
Dan Ross Avatar asked Mar 18 '16 15:03

Dan Ross


2 Answers

clojure.core.memoize has what I need: a memo-clear! function. After requiring [clojure.core.memoize :refer [memo memo-clear!]], the solution looks like this:

  (defn request-app-token
    "Request an app token from FB. Useful for app-global actions, such as creating test users."
    [client-credentials]
    (-> {"client_id" (:client-id client-credentials)
          "client_secret" (:client-secret client-credentials)
          "grant_type" "client_credentials"}
         (form-encode)
         ((partial str fb-graph-api "/oauth/access_token?"))
         (client/get {:throw-entire-message? true})
         :body
         (json/read-str)
         (get "access_token")))

  (def get-app-token (memo request-app-token))

  (defn with-app-token [client-credentials f]
    (try (f (get-app-token client-credentials))
         (catch Exception e ; I know I should be more specific here, I only want to catch HTTP 400 responses
           (memo-clear! get-app-token client-credentials)
           (f (get-app-token client-credentials)))))
like image 146
Dan Ross Avatar answered Nov 20 '22 05:11

Dan Ross


clojure.core.memoize also has ttl function which supports caching the value for configurable time.

If the token is valid for 60 minutes, then you can use the ttl function as follows

(def get-app-token (memo/ttl request-app-token :ttl/threshold 3600000))
like image 2
A developer Avatar answered Nov 20 '22 05:11

A developer