beginners

Noel Rivas 2025-06-13T15:00:03.747349Z

What REPL-related concepts or operations have you found confusing, or have been frustrating? I'm thinking of putting together a post or video about those that I initially found confusing, so I'm wondering about others' experience πŸ‘€

Krishnansh Agarwal 2025-06-15T07:49:34.799649Z

There are questions I feel difficulty with πŸ˜…

seancorfield 2025-06-15T16:22:34.363519Z

@krishnansh710 If you watch that whole London Clojurians talk/video, I'll be interested to hear whether it helps answer those Qs for you, and what specifics you still want more detail on afterward?

πŸ‘ 1
daveliepmann 2025-06-20T07:27:32.087759Z

@krishnansh710 Jack Rusher recently gave a talk walking through his use of the REPL for a boring task: https://www.youtube.com/watch?v=i_dUvhEIGBQ

Krishnansh Agarwal 2025-06-17T04:36:26.016009Z

Thanks @seancorfield I will try to find time on weekends and do it!

seancorfield 2025-06-14T15:10:18.010889Z

@krishnansh710 Are you asking that as a question, or just suggesting those as topics for Noel's video?

seancorfield 2025-06-14T15:12:23.529619Z

(In my London Clojurians talk/video I highlighted the differences between a Clojure REPL and what other languages call a "REPL", and tried to cover some of those other points too to some degree -- but it's a long video and I think a lot of video consumers don't have the patience for an hour of video these days! πŸ™‚ )

2025-06-13T15:20:54.134069Z

not what you asked but related: there's a splash about find-doc, apropos, dir, doc, source , javadoc etc. but it's easy to just ignore boilerplate - programs give us useless walls of text all the time. it's worth calling attention to those functions and showing how useful they are

πŸ’― 2
Noel Rivas 2025-06-13T15:32:12.309979Z

TIL! I wasn't aware of some of them; thank you for mentioning those.

2025-06-13T15:34:47.220319Z

it might be the splash has changed since I last looked at it closely

2025-06-13T15:35:10.248739Z

maybe it was a lein thing, clj and clojure have no splash

2025-06-13T15:36:31.082359Z

probably a good idea to cover most of the functions in repl-requires since they are there specifically when you get an interactive repl

user=> clojure.main/repl-requires
[[clojure.repl :refer (source apropos dir pst doc find-doc)] [clojure.java.javadoc :refer (javadoc)] [clojure.pprint :refer (pp pprint)] [clojure.repl.deps :refer (add-libs add-lib sync-deps)]]

πŸ‘€ 1
Noel Rivas 2025-06-13T15:36:36.535119Z

lein repl does show a splash message that mentions some of those functions. clj repl does not.

2025-06-13T15:37:16.239249Z

also, a nice trick is (apply require clojure.main/repl-requires) to make any namespace useful as a working namespace in the repl

πŸ’‘ 2
2025-06-13T15:38:46.171999Z

clojure is one of the best explorable / self documenting languages for figuring things out without leaving a repl

Noel Rivas 2025-06-13T15:41:23.122159Z

I'm still discovering that part, but, it is! It's one of the main motivations that brought me to clojure.

Noel Rivas 2025-06-13T15:43:08.684759Z

For this video, though, I would focus on more basic (or fundamental) things that are often taken from granted in tutorials and posts, but whose understanding is empowering: just what the REPL is, what happens when, say, Calva connects to a running nREPL, what happens when you "jack-in", etc.

Noel Rivas 2025-06-13T15:44:08.433269Z

Showing the potential is important, though, so I'll think about weaving some of the exploration tools in the demo πŸ€”

2025-06-13T15:44:14.400459Z

IMHO the tools clojure provides for introspection are simpler than the editor tooling stuff (classic simple vs. convenient dichotomy I guess)

2025-06-13T15:45:04.779229Z

I guess the hard part is a new simple thing you have to learn is more work mentally, than a thing you already know that is extremely complicated

Noel Rivas 2025-06-13T15:45:56.102629Z

are you comparing between REPL via CLI vs REPL via Calva or the like, or comparing between clojure's introspection vs, say, TypeScript IDE tooling?

Noel Rivas 2025-06-13T15:46:52.042839Z

Yeah, Simple made Easy πŸ™‚

βž• 1
2025-06-13T15:47:19.626389Z

I'm comparing what I experience when using cider / calva / whatever vs. what I experience when I use the tools built into the repl. this includes the fact that the editor tools in my experience break frequently, and broken complex things are a lot worse than broken simple things

2025-06-13T15:48:15.768259Z

but I don't need to backseat drive your tutorial, I'm just very opinionated about this haha

Noel Rivas 2025-06-13T15:51:08.655569Z

haha, nah, I appreciate the insights; I'm learning new things here β€”which is the main drive behind even attempting the tutorial; I'm a beginner myself πŸ€·β€β™‚οΈ

2025-06-13T15:54:50.078539Z

I came to this approach of preferring the built in repl tools because I spent a lot of time at work helping coworkers understand clojure, and it was easier to know the repl built ins and show them things on their machine, rather than force them to use my tool stack or make them watch me do it on my machine

2025-06-13T15:55:55.414739Z

also the repl stuff works on a remote machine (to diagnose things that only happen on deploy and never locally), and the tools often don't

2025-06-13T16:08:11.740099Z

oh - I just remembered another repl thing that's super useful

(require 'some.ns 'my.other.ns :reload)
this + up-arrow to navigate repl history can be faster than editor driven reload, especially if changing multiple files together

πŸ™Œ 1
seancorfield 2025-06-13T19:29:41.533129Z

Not REPL, but CLI-related:

# show functions available in deps tool:
clojure -X:deps help/dir
# find out more about a specific function in deps:
clojure -X:deps help/doc :fn find-versions
# now try that function:
clojure -X:deps find-versions :lib ring/ring-defaults

πŸ‘€ 1
2025-06-13T19:30:36.256209Z

is find-versions a placeholder here?

seancorfield 2025-06-13T19:30:57.132349Z

And if you work on a project with a :build alias and build.clj:

clojure -A:deps -T:build help/dir
# or help/doc

seancorfield 2025-06-13T19:31:59.031239Z

I'm going to make an updated short version of my REPL-Driven Development video with my latest Clojure/editor setup, and that's one of the commands I will show in the video.

πŸ™Œ 1
seancorfield 2025-06-13T19:32:08.546509Z

> clojure -X:deps find-versions :lib ring/ring-defaults
{:mvn/version "0.3.0"}
{:mvn/version "0.3.1"}
{:mvn/version "0.3.2"}
{:mvn/version "0.3.3"}
{:mvn/version "0.3.4"}
{:mvn/version "0.4.0"}
{:mvn/version "0.5.0"}
{:mvn/version "0.6.0"}

βœ… 1
seancorfield 2025-06-13T19:33:25.157029Z

In my next.jdbc project:

> clojure -A:deps -T:build help/dir
ci
deploy
test

Noel Rivas 2025-06-13T19:44:26.735259Z

@seancorfield your https://www.youtube.com/watch?v=gIoadGfm5T8&t=2621s has been a great source as I try to wrap my head around all of this πŸ™‚

seancorfield 2025-06-13T19:45:04.102689Z

A new, simpler one is coming "soon". Just VS Code + Calva + Calva Power Tools, and the CLI.

Noel Rivas 2025-06-13T19:45:35.820049Z

And now I'll add those commands above to the list of things to digest

Noel Rivas 2025-06-13T19:45:48.197819Z

Looking forward to that one!

seancorfield 2025-06-13T19:45:54.021979Z

ctrl+shift+space d s is the default hot key in the Power Tools to sync deps, and accepts an optional alias (so you can sync your :test deps, for example).

seancorfield 2025-06-13T19:46:11.283639Z

(assuming you're using Clojure 1.12 of course)

Noel Rivas 2025-06-13T19:47:47.968789Z

by sync deps you mean loading them from deps.edn instead of using add-libs?

seancorfield 2025-06-13T19:53:47.983339Z

Well, sync'ing what is in deps.edn with what's loaded into your REPL -- so the workflow is: edit deps.edn, sync deps, now you can require those deps. If you're just experimenting, add-lib / add-libs is fine, but I think it's good to keep records of your experiments, so adding a new dep under an alias in deps.edn and then sync'ing it means you keep the dep listed for next time.

βœ… 1
2025-06-13T19:58:56.208639Z

yeah, I think the main use for add-lib in a repl is to pull in debug specific deps

Noel Rivas 2025-06-13T20:00:46.689979Z

Yup. IIRC, in Sean's video, he uses add-lib and mentions the need to sync that to deps.edn manually. I don't know if the tooling has changed and now allows loading from deps but didn't before, or if it was just part of the demonstration flow in that case.

seancorfield 2025-06-13T20:01:10.584279Z

That was alpha tooling that was only on a branch. Prior to Clojure 1.12.

seancorfield 2025-06-13T20:01:31.848619Z

(and it was add-lib only as I recall back then... maybe...)

seancorfield 2025-06-13T20:02:07.026619Z

I rewatched the video the other night and all that #_ shenanigans... ugh! Glad we don't have to do that any more.

Noel Rivas 2025-06-13T20:02:10.444559Z

Ah, that's the reason you mentioned 1.12 earlier; gotcha

seancorfield 2025-06-13T20:02:23.459349Z

It's why I'm making a new video.

Noel Rivas 2025-06-13T20:04:01.235589Z

yeah, that seemed like a lot of friction

Noel Rivas 2025-06-13T20:06:30.489519Z

@seancorfield are you planning on making a similar session for the new video?

dpsutton 2025-06-13T23:16:03.568709Z

just want to chime in and say 1000% agree with @noisesmith. using the language built in tools is amazing. makes it easy to find your way around a dev setup or connected to an uberjar. I have C-c h bound to require the repl utils in a namespace and use them constantly. Find-doc is a super power, if a bit verbose. Apropos and dir should be everyone’s first line of learning. Or at least not overlooked πŸ™‚

βœ… 1
seancorfield 2025-06-13T23:30:58.310139Z

@hola I'll do it live on Zoom and record it, but it won't be part of a user group meeting or anything organized. Not sure when. Probably next week, in an afternoon or maybe the evening.

πŸ™‚ 1
Krishnansh Agarwal 2025-06-14T04:18:22.793679Z

What is a REPL, benefits of repl driven development, how to use repl driven development in Clojure with a text+editor. Most used functionalities

yuhan 2025-06-17T18:41:09.934949Z

bit late to this thread but I'll chime in that @seancorfield's hour-long videos are the gold standard if you're already invested in Clojure and looking for a comprehensive breakdown of REPL workflows, but my go-to reference for quickly showing off what RDD 'feels like' viscerally has been this demo by @tonsky (for even shorter attention spans, forward to 1:30 where the interesting bits start) https://www.youtube.com/watch?v=XEMI5-MBgaM

πŸ†’ 1
2
yuhan 2025-06-17T18:42:19.391809Z

Also the bonus fact that it's 9+ years old at this point but indistinguishable from modern Clojure is a nice nod to language stability :)

Trev 2025-06-13T00:51:34.336679Z

Is there a trick to getting an accumulator from a go-loop instead of the ManyToManyChannel object itself?

gaverhae 2025-06-13T12:04:37.275579Z

If you're fine with process-api-chunk being a "normal", blocking function, and you only want to use core.async so it can do its own job in parallel internally, you could write it as:

(defn process-api-chunk
  [callouts]
  (let [c (chan)]
    (go (doseq [callout callouts]
          (>! c (send-soap-request "getAccountDetailList" callout))))
    (<!! (go-loop [responses []]
           (if-let [res (<! c)]
             (recur (conj responses (:body res)))
             responses)))))
Assuming this is Clojure, and not ClojureScript, as <!! does not exist in JS.

2025-06-13T13:12:15.369479Z

looking at this again after sleep / brain reset. you should avoid doing io inside go blocks, they use a limited thread pool that can get clogged up easily and should be used for coordination, not work. you can use thread which runs in an expandable thread pool and returns a channel

2025-06-13T13:13:16.454759Z

(>! c (<! (thread (send-soap-request ...))))

Trev 2025-06-13T16:08:25.743359Z

Here's the tricky bit, I want the responses back. I did take the loop out of the go block and used blocking <!! but the return value doesn't happen and the repl hangs. I got my "Clojure for the Brave and True" book handy and I need to read the parallel/async chapters. In terms of just getting the job done I may just use a normal loop and return the mapped data

2025-06-13T16:12:19.928339Z

if the go-loop isn't exiting, then you are into debugging territory. because async + println is a mess, and so is async + step debugging, it can be useful to use an atom as a sort of log: (def event-log (atom [])) then inside other code: (swap! event-log conj {:event "add-to-queue" :item {:a 0} ...})

2025-06-13T16:13:22.176069Z

then you can use standard clojure functions to investigate what did or didn't happen, or how many times - frequencies group-by etc.

Trev 2025-06-13T16:27:29.060189Z

Not even a go-loop, it's just a loop

Trev 2025-06-13T16:29:17.293609Z

(defn process-api-chunk [callouts]
  (let [c (chan)]
    (thread (doseq [callout callouts]
              (println "Loading a callout")
              (>!! c (send-soap-request "getAccountDetailList" callout))))
    (loop [responses []
           callout 0]
      (if-let [res (<!! c)]
        (do
          (println "Received response from callout " callout)
          (recur (conj responses (:body res))
                 (inc callout)))
        responses))))

Trev 2025-06-13T16:30:55.715019Z

I then see this with (def responses (process-api-chunk (take 3 prepared-chunks)))

Loading a callout
Received response from callout  0
Loading a callout
Received response from callout  1
Loading a callout
Received response from callout  2
(we hang here)

2025-06-13T16:32:43.067639Z

this is because you don't close c, so res is never nil

Trev 2025-06-13T16:32:58.905729Z

Heheheh

Trev 2025-06-13T16:33:25.504199Z

I thought I might be gapping with close!

2025-06-13T16:33:43.543389Z

if you add that after the doseq, inside the same thread, the code should work as written

Trev 2025-06-13T17:31:50.788779Z

OMG that was the missing piece. I should never try to work while exhausted. Thanks @noisesmith

Trev 2025-06-13T17:32:07.742639Z

(defn process-api-chunk [callouts]
  (let [c (chan)]
    (thread (doseq [callout callouts]
              (println "Loading a callout")
              (>!! c (send-soap-request "getAccountDetailList" callout)))
            (close! c))
    (loop [responses []
           callout 0]
      (if-let [res (<!! c)]
        (do
          (println "Received response from callout " callout)
          (recur (conj responses (:body res))
                 (inc callout)))
        responses))))

2025-06-13T17:33:44.981339Z

right now it's not using much of the core.async machinery, I bet you can come up with a simplified version of this code with less blocking operations now that you have a version that works

2025-06-13T17:34:50.484719Z

for example thread returns a channel, so ( can be used to wrap the io operations inside go or go-loop instead of wrapping the whole doseq

Trev 2025-06-13T17:35:49.857439Z

Huh. Okay πŸ™‚ I'll take a kick at it

2025-06-13T17:36:16.240949Z

there are also other functions that simplify some of the patterns if you check out the API

Trev 2025-06-13T17:36:53.607039Z

It's as I said I need to actually reed my book instead of trying to jump into everything 🫠

2025-06-13T17:37:41.708189Z

that's also useful - but learning is a lot more powerful when you do things and experience for yourself what kinds of things work or don't work

Trev 2025-06-13T17:39:00.054399Z

Totally, nothing works better for me than actually having a reason to write a thing. I've had the book for two years. Having a real problem to solve actually got me writing

Trev 2025-06-13T00:52:50.189249Z

For example:

(defn process-api-chunk [callouts]
  (let [c (chan)]
    (go (doseq [callout callouts]
          (>! c (send-soap-request "getAccountDetailList" callout))))
    (go-loop [responses []
              callout 0]
             (if-let [res (

Trev 2025-06-13T00:54:14.050909Z

This returns: #object[clojure.core.async.impl.channels.ManyToManyChannel 0x2be2800c "clojure.core.async.impl.channels.ManyToManyChannel@2be2800c"] instead of the array of responses.

2025-06-13T00:55:10.632669Z

go-loop returns a channel, otherwise the code wouldn't return until the loop exited

2025-06-13T00:55:32.436389Z

channels are how we manage asynchronous events without blocking

2025-06-13T00:55:56.795719Z

you can use a blocking deref on the channel to wait for it

Trev 2025-06-13T00:56:05.255599Z

It's definitely doing the callouts though

Trev 2025-06-13T00:56:12.408729Z

So at least I'm that far

2025-06-13T00:56:27.411979Z

sure, but it won't have an exit value until it exits

2025-06-13T00:56:38.611509Z

but you can still check or even wait for the channel

πŸ‘ 1
Trev 2025-06-13T00:57:12.711499Z

Blocking deref is <!! outside of a go block?

2025-06-13T00:57:30.664099Z

right

2025-06-13T00:57:43.179609Z

you can also check if it's ready or not and conditionally deref

Trev 2025-06-13T00:59:00.740049Z

So where I doseq Load up the channel with tasks, I can wait until the channel is ready (all tasks are done)?

2025-06-13T00:59:02.630709Z

iirc poll! works outside a go

2025-06-13T00:59:47.524659Z

well, channels are meant to queue up tasks for you, but you can do some tricks with pipelines and such if you want to control how many messages can be in flight

2025-06-13T01:01:32.959589Z

one thing I found very helpful when learning how to use core.async was opening two or more repls to the same clojure instance, so one could block on a channel in one repl while interacting with other stuff in the other

βœ… 1
Trev 2025-06-13T01:06:40.618169Z

Good to know. Thanks for the tip. Simply swapping out the go-loop for a loop with a blocking take is not doing it for me and my brain is burnt out. I think it's time to rest.

2025-06-13T01:07:44.982279Z

yeah, async code can twist your brain a lot it's good to draw a diagram on paper - an entailment graph with circles for go bodies and lines with arrows for channels

2025-06-13T01:08:39.925449Z

for this case, you might try: create the go-loop first, store the channel it returns in a def then do the doseq (you can do it outside a go block even, depending on your needs) then check the status of the go-loop

Trev 2025-06-13T01:09:59.178199Z

You'd think I'd be used to this by now given how much JS I write.

2025-06-13T01:10:41.516939Z

async breaks many of the things we take for granted in order to understand code