1

First, I have no experience with CS and Clojure is my first language, so pardon if the following problem has a solution, that is immediately apparent for a programmer.

The summary of the question is as follows: one needs to create atoms at will with unknown yet symbols at unknown times. My approach revolves around a) storing temporarily the names of the atoms as strings in an atom itself; b) changing those strings to symbols with a function; c) using a function to add and create new atoms. The problem pertains to step "c": calling the function does not create new atoms, but using its body does create them.

All steps taken in the REPL are below (comments follow code blocks):

user=> (def atom-pool
  #_=>   (atom ["a1" "a2"]))
#'user/atom-pool

'atom-pool is the atom that stores intermediate to-be atoms as strings.

user=> (defn atom-symbols []
  #_=>   (mapv symbol (deref atom-pool)))
#'user/atom-symbols
user=> (defmacro populate-atoms []
  #_=>   (let [qs (vec (remove #(resolve %) (atom-symbols)))]
  #_=>     `(do ~@(for [s qs]
  #_=>              `(def ~s (atom #{}))))))
#'user/populate-atoms

'populate-atoms is the macro, that defines those atoms. Note, the purpose of (remove #(resolve %) (atom-symbols)) is to create only yet non-existing atoms. 'atom-symbols reads 'atom-pool and turns its content to symbols.

user=> (for [s ['a1 'a2 'a-new]]
  #_=>   (resolve s))
(nil nil nil)

Here it is confirmed that there are no 'a1', 'a2', 'a-new' atoms as of yet.

user=> (defn new-atom [a]
  #_=>   (do
  #_=>     (swap! atom-pool conj a)
  #_=>     (populate-atoms)))
#'user/new-atom

'new-atom is the function, that first adds new to-be atom as string to `atom-pool. Then 'populate-atoms creates all the atoms from 'atom-symbols function.

user=> (for [s ['a1 'a2 'a-new]]
  #_=>   (resolve s))
(#'user/a1 #'user/a2 nil)

Here we see that 'a1 'a2 were created as clojure.lang.Var$Unbound just by defining a function, why?

user=> (new-atom "a-new")
#'user/a2
user=> (for [s ['a1 'a2 'a-new]]
  #_=>   (resolve s))
(#'user/a1 #'user/a2 nil)

Calling (new-atom "a-new") did not create the 'a-new atom!

user=> (do
  #_=>   (swap! atom-pool conj "a-new")
  #_=>   (populate-atoms))
#'user/a-new
user=> (for [s ['a1 'a2 'a-new]]
  #_=>   (resolve s))
(#'user/a1 #'user/a2 #'user/a-new)
user=> 

Here we see that resorting explicitly to 'new-atom's body did create the 'a-new atom. 'a-new is a type of clojure.lang.Atom, but 'a1 and 'a2 were skipped due to already being present in the namespace as clojure.lang.Var$Unbound.

Appreciate any help how to make it work!

EDIT: Note, this is an example. In my project the 'atom-pool is actually a collection of maps (atom with maps). Those maps have keys {:name val}. If a new map is added, then I create a corresponding atom for this map by parsing its :name key.

ifett
  • 255
  • 1
  • 3
  • 8
  • 2
    Creating vars at runtime is almost always a bad idea. Why do you need to do this? – Alex Jun 12 '14 at 15:54
  • @Alex I appreciate your inquiry. The case of creating vars at runtime is an attempt at solving a particular problem. This problem comprises custom user-created queues. The state of each custom queue can be then further modified by the users themselves. So we have a mutable state, which itself holds other mutable states. If a certain state of the queue is achieved, then a specific function is called. I understand now that creating vars at runtime is bad. I'll use Dave Yarwood's solution. – ifett Jun 12 '14 at 17:14

3 Answers3

5

"The summary of the question is as follows: one needs to create atoms at will with unknown yet symbols at unknown times. "

This sounds like a solution looking for a problem. I would generally suggest you try another way of achieving whatever the actual functionality is without generating vars at runtime, but if you must, you should use intern and leave out the macro stuff.

You cannot solve this with macros since macros are expanded at compile time, meaning that in

(defn new-atom [a]
 (do
   (swap! atom-pool conj a)
   (populate-atoms)))

populate-atoms is expanded only once; when the (defn new-atom ...) form is compiled, but you're attempting to change its expansion when new-atom is called (which necessarily happens later).

Joost Diepenmaat
  • 17,633
  • 3
  • 44
  • 53
3

@JoostDiepenmaat is right about why populate-atoms is not behaving as expected. You simply cannot do this using macros, and it is generally best to avoid generating vars at runtime. A better solution would be to define your atom-pool as a map of keywords to atoms:

(def atom-pool
  (atom {:a1 (atom #{}) :a2 (atom #{})}))

Then you don't need atom-symbols or populate-atoms because you're not dealing with vars at compile-time, but typical data structures at run-time. Your new-atom function could look like this:

(defn new-atom [kw]
  (swap! atom-pool assoc kw (atom #{})))

EDIT: If you don't want your new-atom function to override existing atoms which might contain actual data instead of just #{}, you can check first to see if the atom exists in the atom-pool:

(defn new-atom [kw]
  (when-not (kw @atom-pool)
    (swap! atom-pool assoc kw (atom #{}))))
Dave Yarwood
  • 2,866
  • 1
  • 17
  • 29
  • Thank you! This is exactly a drop-in replacement for what I'm trying to achieve. As a someone who has never programmed anything, storing atoms in a map which itself is an atom immediately amazed me. :) – ifett Jun 12 '14 at 17:25
  • 2
    If you're working with multiple mutable "things", refs + dosync seems a better fit to me, but probably you could work out the multiple "things" to be just a map with immutable "things" which resides in just one atom. – Michiel Borkent Jun 12 '14 at 19:38
-1

I've already submitted one answer to this question, and I think that that answer is better, but here is a radically different approach based on eval:

(def atom-pool (atom ["a1" "a2"]))

(defn new-atom! [name]
  (load-string (format "(def %s (atom #{}))" name)))

(defn populate-atoms! []
  (doseq [x atom-pool]
    (new-atom x)))

format builds up a string where %s is substituted with the name you're passing in. load-string reads the resulting string (def "name" (atom #{})) in as a data structure and evals it (this is equivalent to (eval (read-string "(def ...)

Of course, then we're stuck with the problem of only defining atoms that don't already exist. We could change the our new-atom! function to make it so that we only create an atom if it doesn't already exist:

(defn new-atom! [name]
  (when-not (resolve (symbol name))
    (load-string (format "(def %s (atom #{}))" name name))))

The Clojure community seems to be against using eval in most cases, as it is usually not needed (macros or functions will do what you want in 99% of cases*), and eval can be potentially unsafe, especially if user input is involved -- see Brian Carper's answer to this question.

*After attempting to solve this particular problem using macros, I came to the conclusion that it either cannot be done without relying on eval, or my macro-writing skills just aren't good enough to get the job done with a macro!

At any rate, I still think my other answer is a better solution here -- generally when you're getting way down into the nuts & bolts of writing macros or using eval, there is probably a simpler approach that doesn't involve metaprogramming.

Community
  • 1
  • 1
Dave Yarwood
  • 2,866
  • 1
  • 17
  • 29
  • Is this answer is being downvoted because I'm advocating using `eval`, or because I answered the question twice? Speak up, downvoters! – Dave Yarwood Jun 13 '14 at 15:42