As far as code cleanup is concerned, I'd remove the make-memories-constant
macro -- you can just do
(def MEMORIES @MEMORY)
or even
(def MEMORY @MEMORY)
so as not to clutter your namespace with another Var. As long as you put this after all calls to memorize
, this will save a snapshot of MEMORY
with all memorize
d strings inside.
I'd say this is actually pretty clean, with the actual code executing at runtime no different from what it would have been without any memorize
-ing...
Another approach would be to prepare a sort of "memorisation framework" as a separate namespace exporting a macros called setup-memorization
, say, which could look something like this (just a rough sketch which won't work without some polishing... updated with a (not so thoroughly) tested version -- this actually works!... still, please tweak it to your needs):
(ns memorization-framework)
(defmacro setup-memorization []
(let [MEMORY (gensym "MEMORY")
MEMORIES (gensym "MEMORIES")]
`(do (def ~MEMORY (atom []))
(defmacro ~'memorize [s#] (swap! ~MEMORY conj s#) s#)
(defmacro ~'get-memory [] @~MEMORY)
(defmacro ~'defmemories [~MEMORIES]
`(do (def ~~MEMORIES @~~MEMORY)
(ns-unmap ~~*ns* '~'~'get-memory)
(ns-unmap ~~*ns* '~'~'memorize)
(ns-unmap ~~*ns* '~'~MEMORY)
~~MEMORIES)))))
Then you'd use
the memorization-framework
namespace, do (setup-memorization)
towards the top of your namespace to set things up, call memorize
like you do in your example code to memorise things and finally use end-memorization
defmemories
to store the string collection somewhere in a Var and remove the temporary Var used to store the atom used for construction-time storage along with the ability to make further calls to memorize
from this namespace. (The end-memorization
macro is meant to just return the collection to wherever you call it from when called with no arguments or define a new Var to store it if given a single argument, which must be a symbol to be used to name the Var. Update: I've only tested the defmemories
version, so I'm leaving it here and removing the "in-place return" variant.)
An example interaction at the REPL while in namespace foo
(note I defined setup-memorization
in the user
namespace):
foo> (user/setup-memorization)
#'foo/defmemories
foo> (get-memory)
[]
foo> (memorize "foo")
"foo"
foo> (memorize "bar")
"bar"
foo> (get-memory)
["foo" "bar"]
foo> (defmemories quux)
["foo" "bar"]
foo> quux
["foo" "bar"]
foo> (get-memory)
; Evaluation aborted.
foo> (memorize)
; Evaluation aborted.
Those "Evaluation aborted." messages indicate that calls to get-memory
and memorize
throw exceptions after defmemories
has been called. This is by design, as mentioned above. Also, get-memory
is here mostly to ease testing / debugging.
If you're only going to use all this once, the setup-memorization
approach might be overkill, but I guess it'll remove some boilerplate for you if you use it more.