简体   繁体   English

如何从 Clojure 中的字符串中获取变量的值?

[英]How can I get the value of a variable from a string in Clojure?

I was doing a small project in clojure and I wonder if there is something like this:我在 clojure 做一个小项目,我想知道是否有这样的事情:

(let [myvar "hello"] (println (read-var "myvar")))

where the "read-var" function finds that there is a variable with the name passed as string and returns it's value.其中“read-var” function 发现有一个名称作为字符串传递的变量并返回它的值。

I found this load-string function but it seems that doesn't work with let bindings.我发现这个加载字符串function 但它似乎不适用于 let 绑定。

Thank you!谢谢!

I would say that in case you're in need of this kind of behaviour, you're probably not doing it right.我想说,如果你需要这种行为,你可能做得不对。 in fact i can't even imagine why would someone want to do this in practice事实上,我什至无法想象为什么有人要在实践中这样做

but there is a way但有办法

clojure macros have special implicit parameter, called &env , allowing you to get local bindings. clojure 宏具有特殊的隐式参数,称为&env ,允许您获取本地绑定。 So you could use this feature for local vars resolution at runtime:因此,您可以在运行时将此功能用于本地变量解析:

(defmacro get-env []
  (into {} (map (juxt str identity)) (keys &env)))

notice that this macro doesn't require to know your desired var name at compile time, it rather just lifts the bindings from macro scope to runtime scope:请注意,此宏不需要在编译时知道您想要的 var 名称,它只是将绑定从宏 scope 提升到运行时 scope:

(let [x 10]
  (let [y 20]
    (get-env)))
;;=> {"x" 10, "y" 20}

(let [a 10
      b 20
      c 30
      env (get-env)]
  [a b c env])
;;=> [10 20 30 {"a" 10, "b" 20, "c" 30}]

even this甚至这个

(let [a 10
      b 20
      c 30
      env (get-env)]
  (get-env))
;;=> {"a" 10, "b" 20, "c" 30, "env" {"a" 10, "b" 20, "c" 30}}

(let [x 10] (println ((get-env) "x")))
;;=> 10
;;   nil

so the behaviour is dynamic, which could be shown with this fun example:所以行为是动态的,可以用这个有趣的例子来展示:

(defn guess-my-bindings [guesses]
  (let [a 10
        b 20
        c 30]
    (mapv #((get-env) % ::bad-luck!) guesses)))

user> (guess-my-bindings ["a" "zee" "c"])
;;=> [10 :user/bad-luck! 30]

but notice that this get-env effect is limited to the bindings effective at it's expand-time.但请注意,此get-env效果仅限于在展开时有效的绑定。 eg:例如:

(let [x 10
      y 20
      f (fn [] (let [z 30]
                 (get-env)))]
  (f))
;;=> {"x" 10, "y" 20, "z" 30} ;; ok

(def f (let [x 10
             y 20]
         (fn [] (let [z 30]
                  (get-env)))))

(f)
;;=> {"x" 10, "y" 20, "z" 30} ;; ok

but

(let [qwe 999]
  (f))
;;=> {"x" 10, "y" 20, "z" 30} ;; oops: no qwe binding 

I am not aware of some approach to accomplish this if read-var has to be a function.如果read-var必须是 function,我不知道有什么方法可以做到这一点。 If read-var were a macro and its argument is a string literal it would be possible to implement read-var so that the code that you wrote works.如果read-var是一个宏并且它的参数是一个字符串文字,那么可以实现read-var以便您编写的代码可以工作。 Another approach would be to build a macro read-var that uses eval , but that would also not be possible because eval cannot access local bindings as explained in this answer .另一种方法是构建一个使用eval的宏read-var ,但这也是不可能的,因为eval无法访问本地绑定,如本答案中所述。

The closest I could come up with that (i) implements read-var as a function and (ii) lets you pass runtime values as arguments to read-var is the following:我能想到的最接近的(i) read-var实现为 function 和(ii)让您将运行时值作为 arguments 传递给read-var如下:

(def ^:dynamic context {})

(defmacro with-readable-vars [symbols & body]
  `(binding [context (merge context ~(zipmap (map str symbols) symbols))]
     ~@body))

(defn read-var [varname]
  (get context varname))

and you can now use this code like现在您可以使用此代码

(let [myvar "hello"]
  (with-readable-vars [myvar]
    (println (read-var "myvar")))) ;; Prints hello

The difference compared to your code is that you have to declare the variables that should be readable using the with-readable-vars macro.与您的代码相比,不同之处在于您必须使用with-readable-vars宏声明应该可读的变量。 Obviously, you can build another macro that combines let and with-readable-vars if you like:显然,如果您愿意,您可以构建另一个结合了letwith-readable-vars宏:

(defmacro readable-let [bindings & body]
  `(let ~bindings
     (with-readable-vars ~(vec (take-nth 2 bindings))
       ~@body)))

(readable-let [myvar "hello"]
  (println (read-var "myvar")))

The above code assumes you are not using advanced features such as destructuring for your bindings.上面的代码假定您没有为绑定使用解构等高级功能。

Pretty simple here:这里很简单:

  (let [myvar "hello"]
    (println myvar))
  ;=> hello

Please see this sample project , esp.请参阅此示例项目,尤其是。 the list of documentation.文件清单。


If you really want to pass the name of the variable as a string, you will need the eval function:如果您真的想将变量的名称作为字符串传递,则需要eval function:

(ns tst.demo.core
  (:use tupelo.core tupelo.test)
  (:require
    [schema.core :as s]
  ))

(dotest
  (let [mydata "hello"]
    (is= "hello" mydata) ; works
  ))

(def myVar "hello") ; creates a Clojure 'Var':  tst.demo.coore/myVar

(dotest
   ; will fail if don't use fully-qualified namespace in string
    (let [parsed (clojure.edn/read-string "(println :with-eval tst.demo.core/myVar)")]
      (eval parsed)
      ;=> `:with-eval hello`
  ))

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM