Reputation: 8854
I'm working on a Clojure application that needs to be able to read Clojure code from the shell command line. The application has a -main
function that reads a string passed on the command line containing a single Clojure form, and passes this string to the Clojure function load-string
, which parses the string and executes the code. This runs successfully using both lein run
and using an uberjar.
I've found that the code passed at the command line can include fully-qualified namespace names if the namespaces were required
at the top of the file containing -main
. For example, if the source file begins with
(ns popco.core.popco
(:require [popco.core.reporters :as rpt]))
then the string I pass on the command line can reference my function ticker
by popco.core.reporters/ticker
. However, I can't use the alias rpt
. If I refer to ticker
by rpt/ticker
, I get an exception: java.lang.RuntimeException: No such namespace: rpt
.
I'm guessing that this is because the aliases are only available at compile time, and only one compile time. Since the time at which load-file
compiles the string of code is after the time at which compilation of the source file has been completed, rpt
is no longer available as an alias.
A solution that allows me to use namespace aliases is to duplicate the require
s at the top of the file (which I want for use when running in the repl) within -main
. However, there are several namespaces that I might need to include, and duplicating code is undesirable, of course.
Is there another solution? Some way to make aliases defined once, available both when runnning in the repl and when running code from the command line?
(EDIT: The analysis in terms of time of compilation above can't quite be correct--or maybe I don't understand Clojure compilation. (Well, in fact I don't understand Clojure compilation!) The solution mentioned in two paragraphs above works using a require
statement in the original source code inside -main
, not only with a string passed to load-file
. So that require
is getting compiled when -main
is compiled, I suppose, which would be during the same pass as the top of the source file, I assume. Yet somehow code inside -main
is in scope of the alias from the top of the file if the code was typed into the definition of -main
, but not if it's brought in via load-file
. Yet what's brought in via load-file
is in the scope of the alias when (require '[popco.core.reporters :as rpt])
is literally in the source code for -main
. Why?)
Upvotes: 2
Views: 509
Reputation: 20194
The alias
function, which is the mechanism require uses to create namespace aliases via the :as
key, mutates the current namespace. That is, the namespace that is current at the time alias
runs.
This is why require
embedded into your definition of -main
creates the alias in the way you expect: it is mutating your runtime namespace to include the given alias. Emphatically, this does not necessarily alter the popco.core.popco
namespace! In fact, that is unlikely to be the working namespace at runtime, when -main
is invoked (it would be at compile time, and thus would, as in the ns
macro, mutate the ns being defined).
The key here is to realize that the ns
at runtime is not always the ns
which defined your -main
function, and if you want to mutate the evaluation environment at runtime, you need to either switch to the namespace you are mutating, or mutate the namespace you are in.
Upvotes: 1