在运行时修改表达式 [英] Modifying expressions at runtime

查看:145
本文介绍了在运行时修改表达式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

  user => (defn replace-0 [x](if(= 0 x)1 x))
user => (clojure.walk / postwalk
replace-0'(+ 3(* 4 0)))
(+ 3(* 4 1))
;;太好了!表达式被修改!宏可以清理代码:
user => (defmacro replacer [expr]
`(clojure.walk / postwalk replace-0'〜expr))
user => (+ 3(* 4 0)))
(+ 3(* 4 1))
;;但我真的想评估表达式,而不仅仅是创建它:
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
;;成功!!! ....
;;除非表达式包含仅在运行时已知的值,否则不包括。
;;这是尽管表达式被修改的事实
;;在运行时基于运行时已知的值。

user => ($)
CompilerException java.lang.RuntimeException:无法解析
symbol:a在此上下文中,编译:(NO_SOURCE_PATH:13) :1)

Eval看不到 code>。我试过一千种方法。我遇到错误试图嵌入对象在代码中,无法重新绑定非动态变量,并试图使用非全局变量。我尝试使用 declare 创建动态变量动态生成的名称,我不能让它工作 - 声明工作,但动态标签将被忽略我可以在这几天的某一天发布一个问题。)



实际上,有很多关于这个问题的问题,发现,解决方案提出了解决问题,因为通常有一个更容易的方法。但是解决方法完全依赖于个别问题。 Clojure是一个Lisp,一个homoiconic语言 - 我的程序应该能够动态地修改自己。



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

  user => (defn replace-map [smap expr] 
(clojure.walk / postwalk-replace smap expr))
user => (replace-map'{s(+ 1 s)}'(+ 3 s))
(+ 3(+ 1 s))
;;到现在为止还挺好。
user => (defn yes-but-increment-even
[val sym](if(even?val)sym(list'+ 1 sym)))
user => (defmacro foo [& xs]
`(zipmap'〜xs
(map-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 => (让[a 1 b 2 c 8](替换[a b c](+ a b c)))
(+(+ 1 a)b c)
;;它的工作!基于局部变量修改表达式。
;;我可以用表达式来做事情...
user => (let [a 0 b 5 c 3](str(replacer [abc](+ abc)))
(+ a(+ 1 b)(+ 1 c))

这么近,还有很远...



我立即申请,我正在与ARefs合作:

  user => (defn foo [val sym](if(instance?clojure.lang.ARef val)
(list'deref sym)
sym))
user => (让[a 1 b 2 c 8](替换者[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 [abc](+ abc))))
CompilerException java.lang.RuntimeException:无法解析
symbol:上下文,编译:(NO_SOURCE_PATH:146:1)


解决方案

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

  

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

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



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



问题



这会失败:

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

原因是 eval 查看上下文;也就是说,它看不到 a



The Trick



您可以通过在 a 作为参数的函数中包装您的表达式来解决此问题,do eval ,然后将的值传递给 eval c $ c>。

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

这在运行时肯定是工作的:

 (def my-map {0 1})

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

(foo); => 7

(def my-map {0 2})
(foo); => 4(无需重新定义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))












$ b > 111


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)

解决方案

Your first example doesn't get at what you really want judging from your last examples. In the first example, the replacement values are known at compile time (i.e. expressions of literals), so there is an easier way:

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

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

This works because the replacement map is known at compile (macro-expansion) time.

If replacement depends on values at runtime, you'll need eval.

The Problem

This fails:

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

The reason is that eval does not see context; that is, it can't see the binding of a.

The Trick

You can work around this by wrapping your expression in a function that takes a as an argument, do eval, then pass the value of a to the (outer) function produced by eval.

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

This is definitely working at run-time:

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

More Complex Example

I believe for your last example you are for something like this:

(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

这篇关于在运行时修改表达式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆