I was doing a small project in clojure and I wonder if there is something like this:
(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.
I found this load-string function but it seems that doesn't work with let bindings.
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. 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:
(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. 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. 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. 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 .
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:
(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. Obviously, you can build another macro that combines let
and with-readable-vars
if you like:
(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:
(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`
))
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.