What are the alternatives to using remove-ns to clean up definitions

Now my poor beta testers can test refresh and refresh-all. It only refreshes the clj repl right now. Do you know if CIDER supports refresh for cljs repls?

1 Like

Nice!

As far as I know, the default Duct template makes use of refresh for browser REPLs, and refreshing from the editor then reloads cljs code as well.

1 Like

FWIW, I’ve never needed any sort of “refresh all” in my workflow. I have a REPL running with all the dependencies of our monorepo across a dozen or so subprojects, and it runs for days and days and days. I probably restart it once a week at most…

5 Likes

But you still use refresh? Just checking.

I can’t believe I never knew about cider-refresh… wow!

1 Like

No, no refresh, no refresh all. I just don’t seem to need them.

1 Like

That’s interesting. Have you written about this somewhere? Even if your style of coding is not for everyone, all the time, it seems I could learn a lot from getting to know more about it.

I honestly don’t think I’m doing anything different – I just don’t seem to need a “refresh”. I wonder if folks have rushed to adopt it without waiting to see whether they experience pain without it?

No refresh is mentioned anywhere in the clojure.org https://clojure.org/guides/repl/introduction pages (I just checked). Pretty sure I’ve never heard Stu Halloway mention/show it in his various REPL usage talks? (just searched the https://github.com/matthiasn/talk-transcripts/tree/master/Halloway_Stuart transcripts repo and the only refresh is in one of David Nolen’s talks in a completely different context).

4 Likes

Interesting to hear that you’re not using it. There is Component, though, and the related component.repl/reset, which does something similar. I’m left with a few loose threads, though,

  • Do you use a component system like Component or Integrant? If not, how do you manage stateful components, and the need to reload / refresh those?
  • Do you ever run into problems with multimethods?
  • Do you ever run into ordering problems in namespaces? I’ve had instances where I just define and redefine things in the REPL, and suddenly when I come back after a REPL restart, I realize that I’ve used some var before defining it.

James Reeves (Ring, Component) seems to be influenced by reloads / refreshes in his work. See Advancing Duct.

To be clear, I’m curious, not trying to argue.

I also mostly don’t use refresh. Though sometimes it is useful. So occasionally I do need it.

I think with regards to components, what component really need to be refreshed? That’s mostly necessary if you change the config for it. So normally, I don’t need to do that.

For example, my database doesn’t really need refreshing. Create the connections on start, close them on stop. But once I have it, I can just modify my query code and call it on the connection.

So I guess I just never had components that constantly need to be restarted.

For multi-methods, well, first, I don’t use them a lot to begin with. Also, only the defmulti has to be refreshed. The defmethods don’t need to be (from my memory). So I guess, it is less often I need to change the defmulti and more often I need to change the defmethods.

For ordering, I think this is my biggest use of it. Or actually, more when I have changed things in multiple namespace and don’t remember the require order. That said, you can also often just trust yourself and manually re-eval the namespaces.

1 Like

Yes, we use Component very heavily. No, we don’t use its reset function.

No, I don’t run into problems with multimethods: you can use (def multi-fn nil) to remove a multimethod definition so it can be predictably redefined (putting that above your defmulti should be enough).

No, I don’t run into problems with ordering in namespaces. I suspect that’s a combination of 8+ years of Clojure experience, being careful to check for first use of functions when I do move things around, and never typing directly into the REPL (I always eval code from a source file, so I use “Rich Comment Forms” for non-production/exploratory code in my source files).

Also, we have a stub test namespace for every single source file so when we run clj -A:test:runner we get a failure if there’s an ordering problem – and we run our automated tests a lot, locally! We have a script that generates a stub test namespace for any source file that doesn’t already have one: it requires the source namespace and refers all symbols as a baseline for “can this source file be loaded” successfully.

Perhaps another aspect of my workflow that helps with all this: whenever I change any code at all, I always eval the top-level form that it’s in – so the REPL always has the up-to-date version of my code. That’s just habit, but it’s also part of a good REPL-Driven Development workflow: make small changes and always test every change in the REPL as you go. That means that instead of making a slew of changes across multiple namespaces and hoping you got it all correct, you work from the bottom up, testing each changed file as you go.

13 Likes

Thank you! The trick with (def multi-fn nil) is neat.

How do you avoid the problem of renaming/removing a function and forgetting old references to it? I guess it is the same as with ordering and the stub tests would catch it?

1 Like

Pretty much, yes. Also, when I rename a function, I always do a (global) find on the old name to check I got all instances (and, of course, I’ll eval each changed form into the REPL as I make edits).

To be honest, I don’t find myself renaming functions very often – and I think that’s partly from “playing” with nearly every function via the REPL as I write them, so the names “evolve” to a stable state before I have calls to them elsewhere. In the same way that TDD tends to guide how you design a function’s name and signature – by focusing on usage, rather than implementation.

3 Likes

Neat trick.

I guess (def bla nil) works for all defonce Var?

3 Likes

defonce checks for a root binding and will not overwrite any Var that has one – so, no, the (def bla nil) trick won’t work with defonce, and you’ll have to ns-unmap it.

(it was a good question – I had to go experiment in the REPL to figure out the answer!)

1 Like

I use a function to walk all the namespaces and remove the mappings to the var:


It’s a bit complicated because it handles both Clojure and Clojurescript. A Clojure only version could be very much simplified.

2 Likes

I use remove-ns when I really can’t figure out why something isn’t working, just in case it’s an old binding. I don’t know if I’ve ever used clojure.tools.namespace.repl/refresh. I prefer a minimal toolchain. If I really get stuck, I just restart the REPL. That’s rare. My computer crashes more often than I reset the REPL.

Part of it is laziness. I put clojure.tools.namespace on my mental “check it out” list and just never got to it. But I guess that shows that I never needed it.

Two big reasons to use a tool like refresh are multimethods and protocols. If you redefine the protocol, you have to re-eval all extend-protocol forms, among other issues. But it strikes me that I also rarely use protocols and multimethods, so that may be why I’ve never needed it.

5 Likes

That script to stub test-ns for each namespace sounds really nice. Is that functionality included in tooling anywhere, or available to share?

Here’s the main body of it – you’ll need to set copyright and suffix prior to it:

last_stem=
for f in `find src -name '*.clj'`
do
  stem=`echo $f | sed 's;src/\(.*\)\.clj;\1;'`
  mkdir -p test/`dirname $stem`
  if test ! -f test/${stem}_${suffix}.clj
  then
    if test "$last_stem" != "$stem"
    then
      echo $stem
      last_stem=$stem
    fi
    (
      echo ";; copyright (c) 2019 ${copyright}"
      echo ''
      ns=`echo $stem | sed 's;/;.;g' | sed 's;_;-;g'`
      echo "(ns ${ns}-${suffix}"
      echo "  (:require [${ns} :refer :all]))"
    ) > test/${stem}_${suffix}.clj
  fi
done
1 Like

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.