Hey Jeff, I appreciate the comments. The fact that the Clojure REPL sets up a dynamic context (not just with *ns*, but with many other <https://github.com/clojure/clojure/blob/master/src/clj/clojure/main.clj#L71-L88> dynamic bindings as well) that we take for granted is indeed confusing in the gen-class and other scenarios. If you want to file an issue for the reference docs on the site, the place to do that is here:
https://github.com/clojure/clojure-site/issues Alex On Thursday, June 22, 2017 at 9:24:24 PM UTC-5, Jeff Rabinowitz wrote: > > If I may be so bold to interject -- > > I'm a colleague of Didier's, and I originally brought this to his > attention when I hit this issue at work. I just want to add that part of > the confusion arises from some miscommunication/incomplete information on > the official docs for Namespaces > <https://clojure.org/reference/namespaces>; > > > At the Repl it’s best to use in-ns >> <https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/in-ns>, >> >> in which case the new namespace will contain mappings only for the >> classnames in java.lang. In order to access the names from the >> clojure.core namespace you must execute (clojure.core/refer >> 'clojure.core). The user namespace at the Repl has already done this. > > > > The current namespace, **ns** can and should be set only with a call to >> in-ns >> <https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/in-ns> >> or >> the ns macro >> <https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/ns>, >> both of which create the namespace if it doesn’t exist. > > > I believe this is a major source of the confusion (at least it was for > me). The official documentation *does* mention that in-ns is recommended > in the REPL; and in a subsequent paragraph, the official documentation > *does* state that in-ns (or ns but it macroexpands to in-ns as well so is > just as bad in context) is the preferred way of creating a namespace. But > nowhere on the official documentation page does it qualify that *ns*, as > a dynamic Var, will require curation if bootstrapping an application from > a static context! Instead, users are thrown with the ugly > IllegalStateException("Can't > change/establish root binding of: *ns* with set"), which is fairly > inscrutable in context. (Nowhere else that I'm aware of in the runtime can > a user not have ever created a dynamic var themselves, have manipulated it > without realizing they're using the set! API, and end up trying to set! > it at runtime before calling binding.) It's not a lie or intentionally > misleading, but allows an application developer to walk away with an > incomplete and inconsistent understanding of the operation of those > functions. Somehow the logical decision was taken at every step, but yet > there's room for users to trip up here. > > People who tinker with macros and evals are probably accustomed to > battling issues with dynamic vars, or at the very least are remembering > that dynamic vars have different semantics, and so can easily search online > to discover that they ran afoul of the Var stack frame issue and need to > add a binding. But in-ns is a real head-scratcher! If implemented in > Clojure, it would look like (set! clojure.core/*ns* (create-ns > 'some-namespace)), but because it's defined in RT.java, there's no source > available; what's more, the documentation attached to the Var for in-ns > doesn't allude to the mechanics it employs. > > Hands down I can guarantee all Clojure developers who aren't involved in > the language maintenance will respond to the exception above by thinking, > "Let me check the source using the source macro and see what just > happened." They'll call clojure.repl/source and get -- nothing. Then > they'll check the docs which say -- "Sets *ns* to the namespace named by > the symbol, creating it if needed." While *technically* correct, this is > hands down more likely to be read in the colloquial English meaning of > "casually changes", rather than the Clojure-technical meaning of "Does > something akin to calling `set!`". And they'll either give up after an hour > of fruitless searching, or if they have *a lot of free time*, will spend > several hours poring over compiler source code > <http://justabloginthepark.com/2017/06/18/clojure-and-the-esoteric-mysteries-of-namespaces/> > > until they realize that nobody documented this case; and that the mechanics > at runtime, while internally consistent, are completely counter-intuitive > given the information available on the public documentation. > > I'm glad that you're considering "plugging" the other Clojure entry points > to eliminate this stumbling block altogether. In the meantime, perhaps some > clarification of the documentation (either those attached to the Var > itself, or on the site, or both) are in order? > > Thanks, > > - Jeff > > On Thursday, June 22, 2017 at 3:47:17 PM UTC-4, Didier wrote: >> >> I opened a Jira enhancement: https://dev.clojure.org/jira/browse/CLJ-2185 >> >> Hope I did a good job at it. >> >> On Wednesday, 21 June 2017 18:58:26 UTC-7, Alex Miller wrote: >>> >>> Was just musing some more - the Clojure Java API was added pretty late >>> (~1.6 or 1.7). I wonder if >>> >>> 1) changing the Java Clojure API to ensure a certain binding context and >>> 2) changing the main() method to invoke the Clojure function through the >>> Java Clojure API >>> >>> would be a good end game. Would need a lot more analysis. >>> >>> On Wednesday, June 21, 2017 at 8:52:48 PM UTC-5, Alex Miller wrote: >>>> >>>> >>>> >>>> On Wednesday, June 21, 2017 at 6:09:43 PM UTC-5, Didier wrote: >>>>> >>>>> Great breakdown. >>>>> >>>>> 1. "compile and bootstrap yourself through gen-class" means: you >>>>>> generate a class and then invoke the main() method of that class >>>>>> >>>>> >>>>> I'd go further then just calling main. So I mean you generate a class, >>>>> and every function call from Java using the generated class should be >>>>> executed as if it were executed through clojure.main, so with the >>>>> bindings >>>>> set and inside the user namespace. >>>>> >>>> >>>> I do not think you should treat clojure.main as the gold standard. >>>> clojure.main is a helper program and it makes some choices that may or may >>>> not be what every program wants. There is nothing special about the "user" >>>> namespace for example. Other repls choose other things (pretty sure the >>>> boot repl uses boot.user example). Likewise, there's nothing special about >>>> the set of things clojure.main chooses to bind on startup - lein, boot, >>>> and >>>> other tools make different choices. If anything, I'd say #2 is what you >>>> should consider the most canonical case. >>>> >>>> >>>>> >>>>> >>>>>> 2. "loaded at runtime" means: you (presumably) start the REPL, load a >>>>>> namespace (either from a source file or a class file), then invoke the >>>>>> -main function >>>>>> >>>>> >>>>> Correct. It's the case of a non AOT compiled namespace being loaded >>>>> from one of the many (load) functions of Clojure. Such as: >>>>> >>>>> (load "dda/main") >>>>> (dda.main/-main) >>>>> >>>>> >>>>>> 3. "bootstrapped through clojure.main" means: you invoke clojure.main >>>>>> (a compiled program provided with Clojure) with the -m arg specifying a >>>>>> namespace. This will start the Clojure runtime, load the namespace, then >>>>>> invoke the -main function >>>>>> >>>>> >>>>> Correct. >>>>> >>>>> >>>>>> 4. "bootstrapped through lein" means: I assume this is "lein run" >>>>>> with a :main set or "lein run -m" but I think lein supports specifying a >>>>>> namespace or a function in a namespace or a class with a main() method. >>>>>> Depending which of those you're doing, this is like similar to either #1 >>>>>> or >>>>>> #2 and here lein is effectively doing similar work as #3. >>>>>> >>>>> >>>>> Correct, yes internally lein delegates to clojure.main I believe. >>>>> >>>> >>>> No, I don't think lein uses clojure.main at all. >>>> >>>> >>>>> >>>>> >>>>>> 5. There is a Clojure Java API ( >>>>>> http://clojure.github.io/clojure/javadoc/), but I'm not sure if you >>>>>> are actually referring to this or something else. Doing so would >>>>>> basically >>>>>> mean going through that API to do the same thing as #2. >>>>>> >>>>> >>>>> I am talking about that API. I believe it uses RT under the hood, and >>>>> ends up doing the same thing that #1 does. So in my test, if you did: >>>>> >>>> >>>> This is actually the same as #2, not #1. It's not making an interop >>>> call, it's invoking a var function, just like any other Clojure call. >>>> >>>>> >>>>> IFn require = Clojure.var("dda.main", "-main"); >>>>> require.invoke(); >>>>> >>>>> This will run inside "clojure.core", and will not have any of the >>>>> default bindings set available. >>>>> >>>>> I think if I were to restate your suggestion, I would say that a >>>>>> genclass-compiled main entry point should initialize the Clojure runtime >>>>>> and invoke the code in a binding as if it were being invoked from other >>>>>> Clojure code. I can see some logic in that (although that then also >>>>>> affects >>>>>> #3 and #4 as they go through this code too). >>>>>> >>>>> >>>>> Ya, arguably, I wonder why the clojure runtime doesn't initialize the >>>>> bindings and sets the namespace to 'user before delegating execution back >>>>> to user code. >>>>> >>>> >>>> Again, there is nothing special about user and I wouldn't get hung up >>>> on that. It does seem like it would be reasonable to set up a binding >>>> context for *ns* though so it's set!-able - that's the thing that's >>>> missing. >>>> >>>> >>>>> If it did, then every invocation of Clojure from Java would always be >>>>> bootstrapped in a similar way, and run under an equal context. >>>>> Clojure.main >>>>> wouldn't need to do it anymore. >>>>> >>>>> But there might be a good reason for not doing that inside the clojure >>>>> runtime. In which case, it still leaves open the question of should >>>>> clojure >>>>> invocations through a generated class, and invocations from the Clojure >>>>> API >>>>> mimic the bootstrapping of clojure.main? >>>>> >>>> >>>> I would say, no. :) But it might be reasonable to mimic a Clojure >>>> function invocation that has a binding for *ns*. If you want to file a >>>> ticket, I'm happy to think about it more. >>>> >>>> >>>>> >>>>> P.S.: I also know that in practice, this has not caused that many >>>>> issues, due to the fact that Clojure is now many years old, and I've >>>>> never >>>>> heard of other people complain about the discrepancies between generated >>>>> classes and the clojure API versus clojure.main. So I don't think its a >>>>> pressing issue, but I still feel it could be worth a thought. >>>>> >>>>>> -- You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en --- You received this message because you are subscribed to the Google Groups "Clojure" group. To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.