Mars
Mars

Reputation: 8854

How to make namespace alias available after initial compile time?

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 requires 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

Answers (1)

noisesmith
noisesmith

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

Related Questions