Fork me on GitHub

I’m having a hard time understanding the relationship between a REPL started with clojure.core.server and the REPL that I see in my terminal with the clj command. Here’s a minimal reproduction of what I’m working with:

(ns user
   [integrant.core :as ig]
   [integrant.repl :refer [go reset reset-all]]))

(defmethod ig/init-key ::clj-repl [key opts]
  (clojure.core.server/start-server (assoc opts :name (name key))))

(defmethod ig/halt-key! ::clj-repl [key _]
  (clojure.core.server/stop-server (name key)))

   {:accept        'clojure.core.server/io-prepl
    :address       ""
    :port          6777
    :server-daemon false}}))
I start up Clojure with clj, and I’m prompted with a REPL. I initialize my Integrant system.
Clojure 1.10.3
user=> (go)
init clj-repl
In Emacs, I connect with inf-clojure to localhost:6777. Evaluation works well. Through inf-clojure, I evaluate:
(def foo "bar")
… resulting in the expected prepl response:
{:tag :ret, :val "#'user/foo", :ns "user", :ms 0, :form "(def foo \"bar\")"}
And intuitively, I can now go back to my Terminal REPL (started with clj) and evaluate foo:
user=> foo
Seems like the io-prepl and clj are sharing the user namespace. Makes sense, and the behavior that I was hoping for! However, when I reload with Integrant:
user=> (reset-all)
:reloading (system.main user)
…I evaluate foo again. It errors. This is also what I anticipated from the use of reset-all.
user=> foo
Syntax error compiling at (REPL:0:0).
Unable to resolve symbol: foo in this context
However, back in Emacs + inf-clojure, I can continue to evaluate foo with my io-prepl connection…
(do foo)
…and it still evaluates to "bar". No error.
{:tag :ret, :val "\"bar\"", :ns "user", :ms 0, :form "(do foo)"}
The io-prepl the clj terminal REPL are both in the user namespace… but they’re clearly out of sync. This problem continues with every use of reset-all. It seems that io-prepl is somehow keeping its own state, which isn’t getting reset with the use of clojure.core.server/stop-server. What confuses me most is that it was initially in sync with the clj terminal’s REPL state. After reset-all, they’re out of sync, and each one seems to keep a state of its own. Has anyone seen this before?


That isn't correct


I've not used reset all or whatever


But names, vars, in namespaces are global, in a clojure runtime, and any repls will see the same state


If you think you are seeing something different, you are mistaken in some way


My guess as to how you are mistaken is your repls are not connected to the same clojure process


Oh, that is definitely the case, if you started a repl via clj


That’s the part I don’t really understand. It’s just one Clojure process, started with clj. And the REPLs are in sync until I call reset-all


I think similar kinds of things are possible, but exactly what you described is not, so are you describing exactly what you see, or summarizing/editorializing something more complicated?


E.g. if you hang on to a var object it doesn't magically get replaced with the new var when you reset (which just uninterns all the vars so you get new var objects)


In theory if the reset code maybe un-interned the namespace object itself, maybe you could get something like you've described, but the code reset all calls doesn't do that (ultimately calls this stuff and i just think on theory you could, I think it is likely, in the scenario you described, both repls still wouldn't behave like that


@neil.hansen.31 I think all the reset/reload/refresh workflows are extremely problematic and generally recommend people avoid them. They have weird corner cases and often very counter-intuitive behavior -- and they are simply not necessary if you develop a good REPL-friendly workflow. As to the specific problems you are running into, I'd suggest asking in #integrant because you're doing some stuff that is very specific to that project.

☝️ 1

@U04V70XH6 Thanks for the perspective. I actually thought the reset/reload style was the standard REPL workflow. Can you suggest any resources about the style you suggest?


@neil.hansen.31 Sean's fully capable of answering himself, but… here's a great talk he gave on this very topic 😅


Also, Stu's excellent talk from a few years prior -- here's a link to the transcript if you prefer to read (as I do), which in turn links to the video on Vimeo:


If you're willing to spend money on a course to teach you about this sort of workflow, consider Eric Normand's REPL-Driven Development course:


(I have taken that course and highly recommend it to everyone!)


@U04V70XH6 @UGTAV6LR2 These are perfect resources, thanks for your help.


How often do you use ? Would like to recommend it to those who get themselves into repl difficulties but it looks like it leaves corner cases too.


@U3TSNPRT9 I don't use it at all because it can break code that depends on certain things in the ns being removed. I occasionally use a snippet of code that removes refers, aliases, and interns from a ns:

❤️ 1

That cleans out the "inside" of an ns so you can do "load file" and get a pristine version without breaking any dependencies (because the ns object itself does not go away).


Note that even (require ... :reload) can break dependencies on an ns sometimes (you'll note I have code in that file to (require ... :reload-all) but I almost never use that because it tends to break dependencies) -- I keep meaning to remove it!


@U04V70XH6 Thanks - feel like there should be a "clean namespace" button/shortcut/menu item running this snippet in IDE integrated REPLs (cc @U0567Q30W 🙂). It would help those newbie co-workers who restart the REPL constantly and find it "unreliable" because redefining vars and aliases everywhere is more side-effectful than they realise.


I suspect some kind of confusion and just repls hooked to different processes instead of the same, which reloading won't effect one way or another