In Clojure, we’ve internalized, as Stuart Halloway said, to send things to the REPL, not type into
the REPL. The (sub)expressions we send to the REPL live in actual
files saved on disk, vs. being ephemeral and tied to a REPL connection. Sometimes, those
(sub)expressions come from existing project namespaces. Sometimes, those (sub)expressions don’t fit
any particular namespace. In that case, Stu’s suggestion is to simply append them to
everything.clj
and evaluate from there. That works quite well! If you’re not doing that, consider
starting.
Over the years of using an equivalent of everything.clj
, I’ve observed a few things:
- Certain (sub)expressions are reusable, or can be made reusable. Enough for me, but not
necessarily enough for them to be included in
dev/user.clj
. - Sometimes, a (sub)expression I wrote a while back is almost (maybe with slight modification) exactly what I need now.
- Coworkers might ask for how I do things. This leads to copy/pasting (sub)expressions to Slack, GitHub PRs, etc.
Based on above, I’d like to recommend an addition to Stu’s suggestion.
Commit those file(s) to project’s git repository under your name 1
For example:
$ tree repl/
repl/
└── miro
├── everything.clj
└── reuse.clj
Every other developer, willing to partake, could put their stuff into repl/$name
directory. This
might also be the place for those debugging helpers.
Suggested rules for repl/*
- Don’t add
repl/*
to the classpath. - Don’t review code in
repl/*
. - When changing
src
, there is no need to changerepl/*
. Especially someone else’s REPL scripts! - There is no guarantee that the code in
repl/*
works. - Setup your editor/IDE to ignore searching text in
repl/*
.
The point of everything.clj
is to send individual expressions to the REPL and not the whole file.
Adding those files to the classpath would send the wrong message. To use functions from reuse.clj
in everything.clj
simply load the whole file with clojure.core/load-file
.
The code in repl/*
is mostly write-and-evaluate-once (with few exceptions). There is no need to
expand the effort into making those expressions work again after an incompatible change to src
.
The effort to fix the expression should only be made when you (or someone else) have the need to
evaluate it again.
In fact, there is no guarantee that any particular expression will work. Some might; some might require a tweak or two; some might be completely broken. The value of having them is that you don’t need to start from scratch. Feel free to delete the code that will obviously not be useful anymore. Also feel free to share certain expressions with your coworkers. With the files committed to git, you can simply give them the permalink of the expression.
Accept any change in the PR coming from repl/*
as-is. It’s not production code, it’s not on the
classpath, and won’t enter production in any way. It’s safe to simply accept someone else’s
experiments.
Finally, you might rely on certain tools or IDEs that search for a given text across all text files
in the project. Make sure to exclude repl/*
files when searching to avoid superfluous lines.
Appendix: an example
An example of how the top of everything.clj
might look like. The top might be include something
you’re using every now and again.
(load-file "repl/miro/reuse.clj")
(reuse/start-unless-already-started)
(reuse/connect-flow-storm)
(reuse/start-quietly)
(reuse/restore-logging)
(reuse/stop-job-executors)
(reuse/run-job-now :integrity-check)
(reuse/run-all-migrations)
(reuse/execute-sql-across-all-dbs-and-schemas ["DELETE TABLE IF EXISTS recently_added"])
(reuse/simulate-time-passed 1-day)
(reuse/delete-all db)
(reuse/bootstrap-the-usually-things db)
(reuse/request-body-as-clojure "/tmp/captured.json")
The bottom might be a recent experiment. The code isn’t meant to be production quality. Comments added for the reader.
;; What's the result?
(time-format/parse-local-date "2024-04-24T00:00:00.000Z")
(time-format/parse-local-date "2024-04-23T23:59:59.000Z")
;; Which formatters are available?
time-format/formatters
(time-format/show-formatters)
;; What's my local time zone?
(time/now)
(java.util.Date.)
(System/getProperty "user.timezone")
(java.util.TimeZone/getDefault)
;; Which formatter is the first that parses successfully?
(doseq [formatter-name (map #(get (set/map-invert time-format/formatters) %)
(vals time-format/formatters))]
(try
(println "Succeeded" formatter-name
(time-format/parse-local-date (get time-format/formatters formatter-name)
"2024-04-24T00:00:00.000Z"))
(catch Exception e
(println "Failed " formatter-name))))
Depending on if you use a monorepo or multirepo, this might mean multiple
everything.clj
files. In that case,everything.clj
is not necessarily referring to a global “everything”, but “everything” for that particular project. ↩︎