Clojure 在运行时修改表达式

Clojure 在运行时修改表达式,clojure,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

我已经为此绞尽脑汁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
创建具有动态生成名称的动态变量,但我无法实现这一点——声明起作用了,但动态标记将被忽略(我可能会在这几天发布一个问题)

事实上,有很多问题会碰到这个问题,在我发现的每一个例子中,都有解决这个问题的方法,因为通常有一种更简单的方法。但解决办法完全取决于个别问题。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)

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

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

(let [a 3] (replacer {0 1} (- 10 (* a 0))))
;=> 7
这是因为替换映射在编译(宏扩展)时已知

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

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

这失败了:

(let [a 3]
  (eval 
    (clojure.walk/postwalk-replace 
      {0 1}
      '(- 10 (* a 0)))))
原因是
eval
;也就是说,它看不到
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

你太棒了!我建议对答案进行一些修改,希望你不介意。在您的上一个示例中,您去解决了这个问题(这是我花12个小时在这个问题上的真正原因)。如果你把最后一个例子贴在那里,我会接受你的答案。太棒了。我总是喜欢好的编辑。在这里的最后一个示例中,我忘了包括我的示例
ref
r1
,这就是为什么它看起来像一个打字错误。在你的链接Q/A中,我实际上喜欢你原始答案中的内容,但我不完全理解你的用例。我看看能不能在那边加些什么。