Sunday, February 8, 2015

Using memoization to change a creation function into get-or-create.

N.B. This should work in any language with first class functions, memoization, and immutable values.

tl;dr:  In the past I'd always thought of memoization as a way to save the computer work. It hadn't occurred to me that it could also save me work.

I was re-reading "The Joy of Clojure" and came across a gem I'd missed the first time. Listing 14.12 is described as "A function to create or retrieve a unique Agent for a given player name".

(def agent-for-player
  (memoize
    (fn [player-name]
      (-> (agent [])
          (set-error-handler! #(println "ERROR: " %1 %2))
          (set-error-mode! :fail)))))

;; The above doesn't quite work for me, set-error-handler!
;; doesn't seem to return the agent. Doesn't make the pattern
;; less compelling, though.

The authors comment that this allows you to maintain player-name as an index into a table of agents without having to explicitly manager and lookup agents.

There are two caveats to this approach: 1) player-name must be immutable, and 2) You really need to understand the memoization mechanism.  clojure.core/memoize, for instance, will keep a internal map of args/response until the end of time. You could use http://clojure.github.io/core.memoize/ to modify the strategy if you so choose.

The place where I'd try this first is in what I call "micro-logs". Frequently as I'm working, I want to log some data to a side channel, and this pattern saves having to manage this manually and cluttering up my code.

(def get-or-create-micro-log
  (memoize
   (fn [file]
     (io/make-parents file)
     (.createNewFile file)
     (-> (io/writer file :append true)
         (agent
          :error-mode :fail
          :error-handler #(println "ERROR: " %1 %2))))))

(defn microlog
  "Useful micro-pattern to send off a write to various
  files via agents without having to maintain a lookup
  table. Symlinks can get you into trouble; at a 
  minimum they will duplicate the agent."
  [lg line]
  (let [a (-> (io/file lg)
              (.getAbsoluteFile)
              (get-or-create-micro-log))
        output (str (str/trim-newline line) "\n")]
    (send-off a
          (fn [writer]
            (doto writer
              (.write output)
              (.flush))))))

(defn microlog-all
  [lg all-data]
  (doseq [d all-data]
    (microlog lg d)))



Edits: Used io/make-parents instead of File calls. Changed send to send-off, since this is I/O. Cleaned up creation of agent.

1 comment:

r2terq7czu said...

It’s common information that gambling is now a multibillion-dollar trade around the world. Because of its widespread appeal, luxurious casinos may be be} present in practically every state across the globe. Gamblers can find all want to|they should} satisfy their needs, whether they are desirous to earn a fast buck or simply want to feel the joy, in these casinos. There’s one thing I’ve observed about some folks in Miami, and that is, if it’s not very convenient to them, then what’s 토토사이트 the sense in doing it or going there? No one who lives in Brickell is complaining about finding “park space” to frolic in except people who don’t reside there.