【问题标题】:How can I get the value of a variable from a string in Clojure?如何从 Clojure 中的字符串中获取变量的值?
【发布时间】:2021-07-28 13:42:51
【问题描述】:

我在clojure中做了一个小项目,不知道是否有这样的东西:

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

“read-var”函数发现有一个名称作为字符串传递的变量并返回它的值。

我找到了这个 load-string 函数,但它似乎不适用于 let 绑定。​​

谢谢!

【问题讨论】:

    标签: clojure


    【解决方案1】:
    在这里很简单:
      (let [myvar "hello"]
        (println myvar))
      ;=> hello
    

    请参阅987654321 @,esp。文档列表。


    如果你真的想将变量名作为字符串传递,你将 需要eval函数:

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

    【讨论】:

    • 谢谢,但这不是我要找的,在我的情况下,我需要一个将字符串“myvar”转换为 let 绑定中指定的 myvar 值的函数。
    【解决方案2】:

    如果read-var 必须是一个函数,我不知道有什么方法可以做到这一点。如果read-var 是一个宏并且它的参数是一个字符串字面量,那么就可以实现read-var,这样你编写的代码就可以工作了。另一种方法是构建一个使用eval 的宏read-var,但这也是不可能的,因为eval 无法访问本地绑定,正如in this answer 所解释的那样。

    我能想到的最接近的 (i) 将 read-var 实现为函数,并且 (ii) 允许您将运行时值作为参数传递给 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))
    

    您现在可以像这样使用此代码

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

    与您的代码相比,不同之处在于您必须使用with-readable-vars 宏声明应该可读的变量。显然,如果您愿意,您可以构建另一个组合 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")))
    

    以上代码假设您没有使用高级功能,例如绑定解构。

    【讨论】:

      【解决方案3】:

      我想说,如果你需要这种行为,你可能做得不对。事实上,我什至无法想象为什么有人要在实践中这样做

      但有办法

      clojure 宏具有特殊的隐式参数,称为&env,允许您获取本地绑定。因此,您可以在运行时将此功能用于本地变量解析:

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

      请注意,此宏不需要在编译时知道您想要的 var 名称,它只是将绑定从宏范围提升到运行时范围:

      (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}]
      

      这个

      (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
      

      所以行为是动态的,可以用这个有趣的例子来展示:

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

      但请注意,此get-env 效果仅限于在其展开时有效的绑定。例如:

      (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
      

      但是

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-03-24
        • 2019-06-03
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多