【发布时间】:2021-07-28 13:42:51
【问题描述】:
我在clojure中做了一个小项目,不知道是否有这样的东西:
(let [myvar "hello"] (println (read-var "myvar")))
“read-var”函数发现有一个名称作为字符串传递的变量并返回它的值。
我找到了这个 load-string 函数,但它似乎不适用于 let 绑定。
谢谢!
【问题讨论】:
标签: clojure
我在clojure中做了一个小项目,不知道是否有这样的东西:
(let [myvar "hello"] (println (read-var "myvar")))
“read-var”函数发现有一个名称作为字符串传递的变量并返回它的值。
我找到了这个 load-string 函数,但它似乎不适用于 let 绑定。
谢谢!
【问题讨论】:
标签: clojure
(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`
))
【讨论】:
如果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 宏声明应该可读的变量。显然,如果您愿意,您可以构建另一个组合 let 和 with-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")))
以上代码假设您没有使用高级功能,例如绑定解构。
【讨论】:
我想说,如果你需要这种行为,你可能做得不对。事实上,我什至无法想象为什么有人要在实践中这样做
但有办法
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
【讨论】: