wrongusername
wrongusername

Reputation: 18918

Dynamic let in Clojure?

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

Answers (3)

MHOOO
MHOOO

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:

  1. For some reason passing multiple symbol-value pairs to LET-DYNAMIC does not work as expected. They'll only be bound on a second call to LET-DYNAMIC. This can likely be solved by changing the macro to pass all symbol-value pairs into the first binding form, instead of recursively building up the bindings as I did in the code above
  2. Vars which did not exist before will not be cleaned up. You can fix this by keeping a map of what was defined and what was not.
  3. I have no idea how this will fare in a multi-threaded environment. You'll have to evaluate that on your own.

Upvotes: 2

mikera
mikera

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

Joost Diepenmaat
Joost Diepenmaat

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

Related Questions