I have been banging my head at this for about 12 hours now. All I want is to evaluate an unevaluated/quoted expression that uses variables from the local scope. I know this has to be done at runtime, not in a macro. I've tried to use macros to clean it up, though.
user=> (defn replace-0 [x] (if (= 0 x) 1 x))
user=> (clojure.walk/postwalk
replace-0 '(+ 3 (* 4 0)))
(+ 3 (* 4 1))
;;Great! The expression is modified! A macro can clean up the code:
user=> (defmacro replacer [expr]
`(clojure.walk/postwalk replace-0 '~expr))
user=> (replacer (+ 3 (* 4 0)))
(+ 3 (* 4 1))
;;But I really want to evaluate the expression, not just create it:
user=> (defmacro replacer2 [expr]
`(eval (clojure.walk/postwalk replace-0 '~expr)))
user=> (replacer2 (+ 3 (* 4 0)))
7
user=> (replacer2 (- 10 (* (+ 0 3) (- 2 0))))
6
;; SUCCESS!!! ....
;; Except not if the expression contains values known only at run-time.
;; This is despite the fact that the expressions are being modified
;; at run-time based on values known at run-time.
user=> (let [a 3] (replacer2 (- 10 (* a 0))))
CompilerException java.lang.RuntimeException: Unable to resolve
symbol: a in this context, compiling:(NO_SOURCE_PATH:13:1)
Eval doesn't see the local binding of a
. I have tried a thousand ways. I have encountered errors for trying to embed objects in code, for not being able to rebind non-dynamic variables, and for trying to use non-global variables. I tried using declare
to create dynamic variables with dynamically-generated names, and I couldn't get that to work -- the declarations worked, but the dynamic tag would be ignored (I may post a question on that one of these days).
There are actually quite a few questions on SO that run up against this problem, and in every single instance I found, solutions were presented that worked around the issue, because usually there's an easier way. But work-arounds are entirely dependent on the individual problems. And Clojure is a Lisp, a homoiconic language -- my programs should be able dynamically to modify themselves. There has got to be a way to do this, right?
Another example, this time starting with locally-bound symbols:
user=> (defn replace-map [smap expr]
(clojure.walk/postwalk-replace smap expr))
user=> (replace-map '{s (+ 1 s)} '(+ 3 s))
(+ 3 (+ 1 s))
;; So far, so good.
user=> (defn yes-but-increment-even
[val sym] (if (even? val) sym (list '+ 1 sym)))
user=> (defmacro foo [& xs]
`(zipmap '~xs
(map yes-but-increment-even
(list ~@xs)
'~xs))))
user=> (let [a 3 b 4 c 1] (foo a b c))
{a (+ 1 a), c (+ 1 c), b b}
user=> (defmacro replacer [vs body]
`(let [~'bod (replace-map (foo ~@vs) '~body)]
~'bod))
user=> (let [a 1 b 2 c 8] (replacer [a b c] (+ a b c)))
(+ (+ 1 a) b c)
;; It's working! The expression is being modified based on local vars.
;; I can do things with the expression then...
user=> (let [a 0 b 5 c 3] (str (replacer [a b c] (+ a b c))))
"(+ a (+ 1 b) (+ 1 c))"
So close, and yet so far away...
For my immediate application, I am working with ARefs:
user=> (defn foo [val sym] (if (instance? clojure.lang.ARef val)
(list 'deref sym)
sym))
user=> (let [a 1 b 2 c 8] (replacer [a b c] (+ a b c)))
(+ a b c)
user=> (let [a 0 b (ref 5) c 3] (str (replacer [a b c] (+ a b c))))
"(+ a (deref b) c)"
user=> (let [a 0 b (ref 5) c 3] (eval (bar [a b c] (+ a b c))))
CompilerException java.lang.RuntimeException: Unable to resolve
symbol: a in this context, compiling:(NO_SOURCE_PATH:146:1)