Fork me on GitHub
#clojure
<
2023-09-26
>
Ingy döt Net00:09:43

I just figured out a cool bash command for seeing exactly how your clj or clojure commands are running java in your environment:

(set -x; export SHELLOPTS; clj -M -e 42)
output is here: https://gist.github.com/ingydotnet/069bff86d3dfcc455bfcd292022db31f and java command is here: https://gist.github.com/ingydotnet/069bff86d3dfcc455bfcd292022db31f#file-out-txt-L125

👀 2
nice 5
pesterhazy06:09:13

I had no idea that you can export SHELLOPTS like that, that's very useful

borkdude11:09:23

I'm just getting:

$ (set -x; export SHELLOPTS; clj -Sforce -M -e 42)
+-zsh:8> export SHELLOPTS
+-zsh:8> clj -Sforce -M -e 42
42

borkdude11:09:49

perhaps bash isn't inheriting this from zsh or so

borkdude11:09:02

ah yes, bash -c '(set -x; export SHELLOPTS; clj -Sforce -M -e 42)' worked

Ingy döt Net14:09:45

It's not that bash wasn't picking up zsh's env. It's that zsh doesn't record set in SHELLOPTS

$ (echo $0; echo $SHELLOPTS; set -x; echo $SHELLOPTS)
bash
braceexpand:hashall:histexpand:history:interactive-comments:monitor:vi
+ echo braceexpand:hashall:histexpand:history:interactive-comments:monitor:vi:xtrace
braceexpand:hashall:histexpand:history:interactive-comments:monitor:vi:xtrace
vs zsh:
$ (echo $0; echo $SHELLOPTS; set -x; echo $SHELLOPTS)
zsh

+zsh:2> echo

danm13:09:56

I'm not sure if this is a Clojure q, a Babashka q, or a Maven q, so here goes: I'm trying to change the location of my Maven repository for the purposes of building within a sandbox where the user home directory is non-writable. I also can't edit ~/.m2/settings.xml. I've found various docs online which suggest things like export MAVEN_OPTS="-Dmaven.repo.local=/path/to/local/repo". I can edit my settings.xml locally for testing, and it's also supposed to be configurable in there via the <settings><localRepository> element. Thing is, neither editing settings.xml or setting the env var causes Clojure or Babashka to download deps to the referenced path. They still end up in the default {user.home}/.m2/repository (deleting the existing directory and then just running clj or bb to start a REPL and trigger downloads based on the contents of deps.edn or bb.edn), and I don't know enough about Maven and the Clojure/Babashka dependency fetching to know if I should be doing this a different way, or if it's just not possible, or what... As far as I can tell from the stacktrace the Clojure code is calling out to the Java apache.maven stuff eventually, but I've not yet found where that is done

borkdude13:09:12

Have you tried -Sdeps '{:mvn/local-repo "the-path"} ?

danm13:09:40

Ahha! That is what I was missing. Thanks!

vemv18:09:28

Would you find it acceptable practice if something like cider-nrepl (or any repl, really) added performed an add-tap at startup time, just once, storing any tapped elements for later inspection? Our would-be tap would limit itself to retaining e.g. 8 elements, so memory consumption would be low (except if you happen to tap> truly huge values) Sample code is OP's here https://github.com/clojure-emacs/cider/issues/3055 which I've verified to work. (some more context: while I understand the idea of taps, I've never used taps much, so I'm trying to be cautious)

phronmophobic19:09:45

The repl typically stores up to 3 recent values in *1, *2, and *3. I often work with large values in the REPL and personally, I wouldn't want references to more than 3 values unless it was opt in.

vemv19:09:27

Similarly, I was thinking that 4 would be a more cautious default because of the potential memory usage. But too low of a value could prevent users from seeing how data flows / evolves over time.

phronmophobic19:09:04

I am a fan of many* of the builtin cider tools (like cider-inspect gratitude ). Maybe any tools that use the history would have an easy way to see the history length and an easy way to extend it if they're using those tools.

phronmophobic19:09:44

My main objection to keeping any more references than 3 is for anyone that would be trying to debug an issue related to extra automatic references. To debug the issue, you need to know specifics about general repl tooling and specifics about cider's tooling (which may or may not end up in a bug description).

vemv19:09:28

> I am a fan of my of the builtin cider tools (like cider-inspect gratitude ). Maybe any tools that use the history would have an easy way to see the history length and an easy way to extend it if they're using those tools. In case you missed it from the issue I linked to, I am precisely trying to bridge taps and the inspector :) If you believe that such an integration should be fundamentally different, a post in that GH thread would be much appreciated

👀 1
vemv19:09:49

...Maybe both the tap activation and its size are better explicitly specified by the user. OTOH one tends to want to create things that "just work". It's a hard balance, if one makes it too easy, one may be accused of authoring 'magic workflows'

lukasz19:09:35

I'd say it should be opt in - my own repl helpers also have similar tools for working with tap: https://github.com/lukaszkorecki/rumble/blob/7bead7852ad3c2b22c578925bc3497880f7a95bd/src/r.clj#L201-L222

lukasz19:09:03

(yes, I know that I reinvented bits of CIDER)

cider 1
vemv19:09:19

Yeah, probably it should be disabled by default, but its activation should be very easy, via defcustom

👍 1
seancorfield20:09:59

As a heavy user of Portal who relies on CIDER middleware and Portal middleware, and who wires up tools.logging to tap> into Portal, I would definitely want this new CIDER feature disabled by default. In Portal, it's easy for me to keep as much history as I want but also easy for me to clear that history too. Portal also makes it straightforward to provide your own "tap-log" so you can control how/where tap> values are actually stored (I use two different tap logs already: one for middleware and one for explicit tap> calls).

vemv20:09:00

Got it, appreciated. Curious, what did you mean by middleware?

seancorfield20:09:22

While a small queue will alleviate potential memory issues, it will also potentially make it hard to debug something if you have tap> in a loop or lots of tap> calls executing in the call chain -- so you'll definitely want easy ways to expand/contract/clear the tap log.

seancorfield20:09:42

I'm wondering how you'd go about exposing those sorts of controls? Perhaps in the inspector itself it might be sufficient to turn this on/off and clear the queue -- and use an unlimited queue when it is enabled (disabled by default).

seancorfield20:09:44

(`tap>` drops values if that queue ever gets full -- I don't think I've ever overrun that queue, even with really heavy tap> usage tho')

vemv20:09:00

> I'm wondering how you'd go about exposing those sorts of controls? It might be usable enough with M-x cider-whatever or also with the handy CIDER inspector key bindings (https://docs.cider.mx/cider/debugging/inspector.html#usage). Clients other then Emacs could have a dedicated nREPL op - easy stuff

1
seancorfield20:09:45

I think tap> is awesome 🙂 I've been using it heavily ever since it dropped in an early alpha of 1.10. Glad to hear you're thinking of supporting it in the CIDER inspector! I used REBL (now Morse) for a while, then Reveal, and now Portal -- makes a massive difference to my workflow.

💯 1
vemv20:09:58

I got fed up of typing (def dbg (atom nil)) so many times 😇 There had been a few attempts of a better tap experience in cider, but sometimes it's hard to get things driven to the finish line.

vemv20:09:09

> FWIW, tap> internally uses a 1,024 queue If one taps x and nobody's listening, will x remain in the queue?

seancorfield20:09:56

No, the queue is emptied continuously. See tap-loop just below where I linked to the queue.

seancorfield20:09:29

It will only "back up" if someone adds a tap listener that is slow or gets blocked (since the tap-loop runs on a single thread).

👍 1
vemv20:09:39

> I would definitely want this new CIDER feature disabled by default. Most likely this will be the case. Anyway, is there a big reason? As in, possible drawbacks / dangers? (I'm seeking to possibly gain a less obvious insight)

seancorfield21:09:27

I was thinking of the case where the queue was unbounded (which you may well need for a debugging session), so you want it off by default.

seancorfield21:09:02

(I don't think a bounded queue is going to be all that useful in real world debugging, unless that bound is pretty large)

oyakushev21:09:40

I think keeping each value inside SoftReferences is a fine tradeoff. Values would be there most of the time, but it won't break the program if huge values start coming through the tap and fill up the heap.

💯 1
phronmophobic21:09:46

> it won't break the program if huge values start coming through the tap and fill up the heap. It's not uncommon for me to test to make sure I'm not holding onto references to make sure my program isn't leaking memory. I believe holding soft references would make that harder to test.

phronmophobic21:09:42

I agree that holding extra references for dev tooling can be useful, but I'm not sure it's a good default behavior for every development environment that uses a cider repl.

vemv21:09:56

Thanks! > (I don't think a bounded queue is going to be all that useful in real world debugging, unless that bound is pretty large) It may be a reasonably frequent usage pattern to look at the latest thing? Or the latest few things. It is the type of usage I give to bare atoms, and that I was giving to tap> these days. Maybe once in a while I would want to look a "needle in a haystack" where indeed, anything but a large/unbounded data structure will be useless. Maybe one can document that (when (interesting? x) (tap> x)) is a pattern that would be useful with CIDER's would-be tap. Ultimately, of course, users can always grow, shrink and remove the bound of the data structure.

oyakushev21:09:57

How do you perform such a test usually?

vemv21:09:43

> It's not uncommon for me to test to make sure I'm not holding onto references to make sure my program isn't leaking memory. Most certainly your test (more so your performance-test) setup should be decoupled from CIDER. Not that CIDER necessarily adds a lot of bloat... but I wouldn't run a serious perf test with CIDER in

phronmophobic21:09:43

> How do you perform such a test usually? It depends. Sometimes I use print statements in a finalizer, sometimes, it's via stat tracker that counts allocations and deallocations so I can make sure they match.

seancorfield21:09:11

(Portal uses an unbounded list but it is "off" until you add Portal as a tap listener and you can clear the list easily -- it's a simple model and works well: off, on/unbounded, clear -- so you only need on/off and clear: two controls)

👍 1
1
oyakushev21:09:07

@U45T93RA6 I think the size of the queue is a moot point regarding the memory consumption. 100 small objects may be smaller than 1 big object. It makes sense to limit the queue to some reasonable value, but there is no "safe length" for it

oyakushev21:09:15

@U7RJTCH6J I see; but then, you would have to tap> those objects in order for your testing to break, right?

phronmophobic21:09:48

Also, from the linked issue:

(defn bounded-conj
  [^long n coll x]
  (let [b (== (count coll) n)]
    (cond-> (conj coll x)
      b pop)))
checking if (== (count coll) n) is often a footgun and you're bounds check stops working if x ever exceeds the bounds.

seancorfield22:09:35

There's a bounded-count that could be used but I don't know what Clojure version added it...

phronmophobic22:09:24

> I see; but then, you would have to tap> those objects in order for your testing to break, right? (edited) For some reason, I was under the impression that the proposal would both be adding a tap and automatically tapping results from forms sent to the repl. 😳 nvmd.

phronmophobic22:09:42

The way I would implement bounded-conj is:

(defn bounded-conj
  [^long n coll x]
  (let [b (>= (count coll) n)]
    (cond-> (conj coll x)
      b pop)))
ie. replacing == with >= when comparing the count to the max. actually, I would also truncate the collection to the bounded count size rather than just popping.

phronmophobic22:09:52

bounded-conj assumes that coll never exceeds the bound which I think is a footgun.

seancorfield22:09:28

We tend to leave tap> calls in the code since it's a no-op if there are no tap listeners active. We have several calls active in production, but until we attach Portal, any tap>'d data is thrown away. Then we can debug, with Portal, and then remove the Portal listener and it goes back to being a no-op.

phronmophobic22:09:56

The idea of automatically adding a tap that is opt-out seems potentially problematic, especially since there's no public API to inspect the list of current taps (afaik).

vemv22:09:09

It will be opt-in - that became clear after all this talk :) I can imagine it being particularly bad in a production server (where some people might be tempted to be running cider-nrepl... which I don't recommend at all), given the 'unconditional taps' pattern that Sean describes Lots of good input in this thread - thanks all! :man-bowing: I'll try to make something sensible out of it.

gratitude 2
🙏 2
oyakushev22:09:21

For me personally, an OK experience would be if the tap only started working after I do M-x cider-inspect-tap or something. Once activated, the tapped values are stored in a queue of SoftRefs, and the queue can be navigated. The queue can be quite long since memory woes would be handled by softrefs.

vemv22:09:36

I'd also control initialization with a defcustom, so that it's running from the beginning. It makes sense for a codebase that already has taps - one may want to see stuff tapped before the first use I'm in for SoftRefs. It might slightly complicate the impl and UI when a SoftRef was actually cleared (as it would produce a queue that could look inconsistent: [val, val, <no val because GC>, val, val])

oyakushev22:09:07

> as it would produce a queue that could look inconsistent Yes, it would. For me, navigating the history of values at the same time as GC is constantly triggering has an inherent "here be dragons" disclaimer. Perhaps, somebody would want a different experience.

👍 1
Ben Sless03:09:58

Two things I'd want from this ability to "install" cider's taps, with defcustom to do so automatically, and a different buffer using the inspector's mode, so I can enjoy both inspection and taps

👀 1
Ben Sless03:09:16

And thank you for taking the time to work on this 🙏

jpmonettas10:09:28

from my experience using and developing #C03KZ3XT0CF everyday, which retains every pointer while your app runs in a timeline, having an easy way of starting/stopping this recording, clearing it, and looking at the heap usage has been enough. Imho if it is easily controllable, and "stopped" by default so no surprises, is a nice feature

1
diego.videco19:09:07

Hello, I have a deps.edn based project that slurps a text file that is on the src directory. However when I try to use that project from another one, locally (although ideally it should work from non local repositories aswell), I am getting a error, because the slurp function tries to read the file from within this other project’s src directory. What’s the best way to fix this issue?

vemv19:09:06

It sounds like you want to export (and later slurp) resources rather than files in the filesystem. Are you familiar with them?

vemv19:09:08

The text file would be a resource in the original project, and in the consumer project, you (slurp (io/resource "a.txt")) (don't specify src/) The whole notion of resources is fairly easy to pick up, probably chatgpt can give a decent primer

diego.videco19:09:41

ah that was great, thanks!

🙌 1
practicalli-johnny08:09:21

Include files in the resources directory and add that directory on the project path, then use `http://clojure.java.io/resource to define the file location (relative to resources directory) https://clojuredocs.org/clojure.java.io/resource

👍 1