When I start a repl with lein repl
I can run the function greet
and it works as expected.
(ns var-test.core
(:gen-class))
(declare ^:dynamic x)
(defn greet []
(binding [x "Hello World."]
(println (load-string "x"))))
(defn -main [& args]
(greet))
But if run the code via lein run
it fails with
java.lang.RuntimeException: Unable to resolve symbol: x in this context.
What am I missing?
Is the var x
dropped during compilation, despite being declared, since it is never used outside of the string?
Edit:
@amalloy's comment helped me understand I need to bind *ns*
in order load the string within the expected namespace, instead of a new, empty namespace.
This works as expected:
(ns var-test.core
(:gen-class))
(declare ^:dynamic x)
(defn greet []
(binding [x "Hello World."
*ns* (find-ns 'var-test.core)]
(println (load-string "x"))))
(defn -main [& args]
(greet))
Wow, I've never seen that function before!
According to the docs, load-string
is meant to read & load forms one-at-a-time from an input string. Observe this code, made from my favorite template project:
(ns tst.demo.core
(:use tupelo.core tupelo.test)
(:require [tupelo.string :as str]))
(dotest
(def y "wilma")
(throws? (eval (quote y)))
(throws? (load-string "y"))
So it appears that load-string
starts with a new, empty environment, then reads and evaluates forms one at a time in that new env. Since your x
is not in that new environment, it can't be found and you get an error.
Try it another way:
(load-string
(str/quotes->double
"(def ^:dynamic x)
(binding [x 'fred']
(println :bb (load-string 'x'))) " ))
;=> :bb fred
In this case, we give all the code as text to load-string
. It reads and eval
's first the def
, then the binding
& nested load-string
forms. Everything works as expected since the working environment contains the Var for x
.
Some more code illustrates this:
(spy :cc
(load-string
"(def x 5)
x "))
with result
:cc => 5
So the eval produces the var x
with value 5
, then the reference to x
causes the value 5
to be produced.
To my surprise, the partial load-string
works in a fresh REPL:
demo.core=> (def x "fred")
#'demo.core/x
demo.core=> (load-string "x")
"fred"
So load-string
must be coded to use any pre-existing
REPL environment as the base environment. When using lein run
, there is no REPL environment available, so load-string
starts with an empty environment.