在运行时修改表达式

时间:2014-04-13 06:18:19

标签: clojure

我现在已经大约12个小时了。我想要的只是评估一个使用本地范围变量的未评估/引用的表达式。我知道这必须在运行时完成,而不是在宏中完成。不过,我已尝试使用宏来清理它。

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没有看到a的本地绑定。我尝试了一千种方法。我遇到了尝试在代码中嵌入对象,无法重新绑定非动态变量以及尝试使用非全局变量的错误。我尝试使用declare创建动态生成的动态变量名称,我无法使其工作 - 声明有效,但动态标记将被忽略(我可能会发布一个问题)其中一天)。

实际上有很多关于SO的问题遇到了这个问题,而且在我发现的每个实例中都提出了解决这个问题的解决方案,因为通常会有更简单的方法。但解决方法完全取决于个别问题。而Clojure是一种Lisp,一种同性语言 - 我的程序应该能够动态地修改自己。有得到是一种方法,对吗?

另一个例子,这次是从本地绑定的符号开始的:

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))"

如此接近,但又遥远......

对于我的直接申请,我正在与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)

1 个答案:

答案 0 :(得分:3)

你的第一个例子并没有从你上一个例子中得到你真正想要的东西。在第一个例子中,替换值在编译时是已知的(即文字的表达式),因此有一种更简单的方法:

(defmacro replacer [smap expr]
  (clojure.walk/postwalk-replace smap expr))

(let [a 3] (replacer {0 1} (- 10 (* a 0))))
;=> 7

这是有效的,因为替换映射在编译(宏扩展)时已知。

如果替换取决于运行时的值,则您需要eval

问题

这失败了:

(let [a 3]
  (eval 
    (clojure.walk/postwalk-replace 
      {0 1}
      '(- 10 (* a 0)))))

原因是eval does not see context;也就是说,它无法看到a的绑定。

诡计

您可以通过将表达式包装在以a作为参数的函数中来执行此操作,执行eval,然后将a的值传递给生成的(外部)函数eval

(let [a 3
      f (eval 
          (clojure.walk/postwalk-replace 
            {0 1}
            '(fn [a] (- 10 (* a 0)))))]
  (f a))
;=> 7

这绝对适用于运行时:

(def my-map {0 1})

(defn foo [] 
  (let [a 3] 
       [f (eval 
            (clojure.walk/postwalk-replace 
              my-map
             '(fn [a] (- 10 (* a 0)))))]
    (f a)))

(foo) ;=> 7

(def my-map {0 2})
(foo) ;=> 4 (without having to redefine foo)

更复杂的例子

我相信你的最后一个例子就是这样的:

(defn maybe-deref-expr 
  [vals params body] 
  (let [smap (zipmap params 
                     (map (fn [val sym] 
                            (if (instance? clojure.lang.IDeref val) 
                              (list 'deref sym) 
                              sym)) 
                          vals 
                          params))
        body* (clojure.walk/postwalk-replace smap body)
        gen (eval (list 'fn params body*))] 
    (apply gen vals)))

(def r1 (ref 1))

(def instance (maybe-deref-expr [r1 10] '[a b] '(fn [x] (+ a b x))))

(instance 100)
;=> 111