Hey I've recently gotten into the habit of 'fiddle' files that sit right next to the source .clj files as REPL scratch pads, like
src/app/impl/webserver.clj
src/app/impl/webserver.fiddle
I believe this convention was popularized by Calva (which has a couple of built-in support features: https://calva.io/fiddle-files/) But as noted at the bottom of that page, it doesn't have great linter support, because clj-kondo can't pick up the var declarations and throws unresolved symbol / ns-alias errors all over the place.
Eg. the contents of the fiddle file might look something like:
(in-ns 'app.impl.webserver)
(def $server (start! 8080))
(stop! $server)
; no need for @#'tricks to invoke private fns
(def >ctx (get-ctx @$server))
(validate (::auth/state >ctx)) ;=> true
(-> (slurp (io/resource "log.txt"))
(str/split-lines)
count)
;=> 123
;; random explorations, temporary defs, etc
Wondering if there could be a way for clj-kondo to better support such a workflow?I can sorta understand why the analyzer doesn't handle in-ns forms in general, (lack of determinism? and non-idiomatic), but maybe there could be a config option to treat some files specially based on their extensions?
In effect treating them as if they were just transparently 'tacked on' to the end of the main file, like a sort of gigantic RCF
Why not just create a fiddles/my_fiddle.clj file with (:require [namespace :refer :all])
Mostly DX reasons, it's a much simpler mental model to have it be literally "in the same namespace" - eg. you have access to the same private vars, ns aliases etc, and there's zero friction to copy-pasting subexpressions back and forth
I think of it as basically a persistent REPL session where you'd in-ns into the namespace and have all those things at hand, or extended RCF forms which I'd rather not clutter up the main file with
Why don't you add a (comment) at the end of the namespace and make this your fiddle workspace
that's basically what they are for
again, mostly ease of use / friction reasons - it's nicer to work with top-level forms when they're actually top level, and I don't have to worry about syntax errors etc that might prevent the file from loading, and it's gitignored and excluded from uberjars etc by default
I've used the RCF workflow as you described for years, and this has genuinely been an improvement for my particular style of dev (might not be the same for everyone)
(here's how I described it to Claude)
**Properties:**
- Gitignored, not on classpath (due to `.fiddle` extension), excluded from uberjars.
- Shared namespace = zero friction for cut-paste, preserves requires, aliases, access to private vars etc. for free
- Need not load cleanly - syntax errors, incomplete forms are fine
- Top-level forms (unlike RCFs in main files)
- Exception: wrap dangerous side effects in `(comment)` for safety
**Typical contents:**
- Exploratory snippets with `$` and `>` prefixed vars, used to distinguish temporary REPL bindings from production code
- Side-effectful orchestration commands
- Legacy/alternative implementations kept for reference
- Graduated test cases (before formalizing to unit tests)
**Evolution pattern:**
1. Start with RCFs in main file (informal sanity checks)
2. Move to fiddle when cluttered or tests get involved
3. Eventually formalize to `*-test` namespace with proper `deftest`
the difficulty is that clj-kondo works with a 1-to-1 mapping for namespaces and cache files. edit one namespace, the cache file overwrites. but perhaps we can do something like this:
(ns babashka.impl.repl-fiddle)
(in-ns 'babashka.impl.repl)
where we basically treat in-ns as a refer-all including private vars.
However, that would not work if you used the in-ns ns as an extra "production" extension (like happens with clojure.pprint)I'm open to contributions to improve this
yes that's not the intention here re. the clojure.pprint layout - I'd never want the fiddle contents to affect linting of the main file, it's should be a strictly one-way flow
well, that is how it would work if we supported the clojure.pprint layout and this is actually how clojure itself works, so that would be the consequence of supporting this
you could also stop using private vars and use .impl namespaces
and then use refer all
a bunch of these uses have also been familiarizing myself in legacy codebases where I don't have the ability to restructure the codebase
two things so far.
• There seems an ambiguity in supporting in-ns: you don't want to "add" definitions to the main namespace in clj-kondo's analysis when you are using it from elsewhere, but this is how in-ns works (see clojure.pprint). What people want is not always how Clojure works and clj-kondo tries to analyze in the way Clojure works.
• If you extract tests from these experiments, then you might as well start with a fiddle "test" namespace instead.
I'm open to contributions but "what we want" should be resolved before we even think about implementing anything
Another thing: for other dialects like CLJS in-ns isn't even possible - how do you explore that code? your pattern doesn't work there
Yeah, I had the feeling that supporting in-ns in general like how clojure.core / pprint is defined would be a much more complex undertaking, you'd have to analyze load forms to figure out what order to interleave the files etc.,
Which is why I thought of the workaround of using the '.fiddle' ext as a signal - the semantics could be something really dumb like 'take the contents of the main file and splice them at the start before analysis'? Not sure if that makes any sense with how the underlying impl works
And yeah, I had the feeling that supporting in-ns like how clojure.core / pprint is defined would be a much more complex undertaking, you'd have to figure out the
Probably something that won't be enabled in the default config, and you would specify the mapping yourself eg. {"*.fiddle" "$1.clj"} for flexibility - I'm not familiar enough with clojurescript to know what the semantics would need to involve there
(just a rough sketch obviously, wrote a regex before remembering that they weren't valid edn - could also be inline configs or #_ discarded forms at the top of the file)
Again not claiming that this is a universally applicable or good workflow, just one that I've found to work well for me, (and other folk? enough to warrant editor support in Calva)