Reputation: 18918
I have the following happening in the REPL:
mathematics.core> (let [zebra 1] (resolve 'zebra))
nil
mathematics.core> (def zebra 1)
#'mathematics.core/zebra
mathematics.core> (let [zebra 2] (when (resolve 'zebra) (eval 'zebra)))
1
Basically, I would like to dynamically bind values to variables using something like a let
form, and have functions inside that form be able to access the value the variable is bound to.
mathematics.core> (def ^:dynamic zebra 1)
#'mathematics.core/zebra
mathematics.core> (binding [zebra 2] (when (resolve 'zebra) (eval 'zebra)))
2
binding
seems to do the trick I want, but AFAIK it requires a variable to be defined with the :dynamic
metadata first. I want to be able to use variables that have never been defined before on the fly, and have expressions in the form be able to access that variable as if it were actually defined.
To illustrate, I want something like this:
mathematics.core> (let-dynamic [undefined-variable 1]
(when (resolve 'undefined-variable) (eval 'unresolved-variable)))
1
Is there an easy way to do this? Or a way to accomplish this using macros?
Upvotes: 4
Views: 996
Reputation: 643
While it is not a complete solution, here is an attempt:
(defmacro let-dynamic
([[sym val & more] & body]
`(do (when (not (:dynamic (meta (resolve '~sym))))
(def ~(with-meta sym {:dynamic true}) ~sym))
(binding [~sym ~val]
~@(if (empty? more)
body
`((let-dynamic ~more ~@body)))))))
A small test:
blub> (def ^:dynamic already-dynamic 'dynamic)
#'blub/already-dynamic
blub> (def not-dynamic 'not-dynamic)
#'blub/not-dynamic
blub> (let-dynamic [already-dynamic 2] already-dynamic)
2
blub> (let-dynamic [not-dynamic 2] not-dynamic)
2
blub> (let-dynamic [not-dynamic-and-not-defined 2] not-dynamic-and-not-defined)
2
blub>
There's several problems with this:
Upvotes: 2
Reputation: 106351
This isn't going to work particularly well. If the symbol isn't defined, then the Clojure compiler can't compile any code that uses it. You might be able to get some kind of hack working with macros that call def lazily when needed, but it would be some pretty nasty code.....
I would suggest just using binding, and define your vars in advance. You should be able to write your code in the way that this works.
I think it's a bad idea to define variables "on the fly". I don't think you should ever really need this - if you are using the variable in the code, surely it is easy enough just to do a (def ^:dynamic ...)
beforehand for each variable that you use?
Upvotes: 4
Reputation: 17773
I want to be able to use variables that have never been defined before on the fly, and have expressions in the form be able to access that variable as if it were actually defined.
This doesn't look to me like a good match for clojure's vars or let-bound values, and if you're generating and eval'ing the whole form on the fly anyway, why not use a simple map to store the symbol -> value mappings and replace the whole resolve/eval scheme with a map lookup? That way you can generate arbitrary symbols on the fly without any obscure namespace trickery that might break your code in some hard to find ways:
(let [my-resolve {'zebra 1}]
(println "zebra is " (my-resolve 'zebra)))
Upvotes: 3