clojure

Jim Newton 2025-11-28T10:31:42.994989Z

I’m trying to write a macro, and I’d like to parse a lambda-list. ie. not the argument list at the callsite, but rather the argument list at the declaration site. Something that looks like this [a1 a2 & {:keys [a b c] :as args}] what is the correct way to find the tail of the vector which starts with the & symbol? or falsey if there is no such & in the lambda-list In CommonLisp the member function returns such a tail.

borkdude 2025-11-28T10:34:42.419569Z

In Clojure you just scan for & and drop the rest.

user=> (drop-while #(not= '& %) '[a b & {:keys [dude]}])
(& {:keys [dude]})

Jim Newton 2025-11-28T10:36:28.562839Z

yes chatgpt recommends drop-while . seems strange. but ok it’ll work.

borkdude 2025-11-28T10:36:46.462809Z

why is this strange?

Jim Newton 2025-11-28T10:37:22.841059Z

well it’s negative logic, and requires a predicate.

Jim Newton 2025-11-28T10:37:43.325859Z

just that in CL its sort of a primitive and something one does often.

Jim Newton 2025-11-28T10:38:26.063689Z

easy enough anyway, its a one-liner… no big deal of course.

borkdude 2025-11-28T10:38:32.073179Z

in clojure most stuff is just data, no special primitives needed

Jim Newton 2025-11-28T10:40:11.971709Z

clojure doesn’t seem to be optimized for parsing lisp code. I suspect you’ve probably built some self-libraries for parsing clojure code in a declarative way, judging from all the clojure parsing your magnificent code does.

p-himik 2025-11-28T10:54:29.314039Z

> clojure doesn’t seem to be optimized for parsing lisp code. That's true. I'd say it's optimized for other things, and those are at odds with parsing lists, so same reason for why we don't have e.g. find-first that returns the first item in a collection that matches a predicate.

yuhan 2025-11-28T11:35:40.728729Z

https://github.com/clojure/core.specs.alpha/blob/master/src/main/clojure/clojure/core/specs/alpha.clj Here's how it's done in clojure.spec if you're interested - the macroexpansion system uses these 'natively' to surface syntax errors

yuhan 2025-11-28T11:38:38.188409Z

Malli also has something similar in the malli.destructure ns, both approaches are pretty declarative

✔️ 1
Jim Newton 2025-11-28T11:35:07.024989Z

we talked a little bit about this in a thread recently, but not explicitly. so I’m posting the question here I’m wondering what are the semantics of :arglists which I see in core.clj from time to time. Here is an example from core.clj

(defmacro
  ^{:private true}
  def-aset [name method coerce]
    `(defn ~name
       {:arglists '([~'array ~'idx ~'val] [~'array ~'idx ~'idx2 & ~'idxv])}
       ([array# idx# val#]
        (. Array (~method array# idx# (~coerce val#)))
        val#)
       ([array# idx# idx2# & idxv#]
        (apply ~name (aget array# idx#) idx2# idxv#))))
the macro goes through great effort of tilde-quote to prepare what looks like something used for documentation purposes. Does :arglists have actual semantics? If I include :arglists in a macro expansion, to I really need to do all this work? In my case it is a bit diffcult (of course not impossible) as I don’t have the actual variable names, I’d need to parse them out of a vector provided at the macro call-site. so I’ll need to maticulously generate the tilde-quotes programmatically.

teodorlu 2025-11-28T11:43:04.762679Z

> Does :arglists have actual semantics? It does influence the generated docstring. https://blog.agical.se/en/posts/keeping-the--arglists-of-clojure-functions-dry/ is a good example of when this is useful.

Jim Newton 2025-11-28T11:46:53.395019Z

I guess you mean the docstring generated by cider (and friends) or is it the docstring generated by clojure itself?

valerauko 2025-11-28T11:47:19.048329Z

it's definitely used somewhere inside the compiler machinery. i stepped on a landmine with this when writing a macro to generate "smart" wrappers for sql queries. so as long as you write them correctly, it can be very nice documentation, but if you screw them up you can break more than you expect https://clojurians.slack.com/archives/C03S1KBA2/p1745998586840819

👍🏻 1
teodorlu 2025-11-28T11:50:04.017909Z

> I guess you mean the docstring generated by cider (and friends) or is it the docstring generated by clojure itself? It changes :doc metadata on the var directly, I think, which is in turn used by cider and friends.

yuhan 2025-11-28T11:51:22.507399Z

Also worth noting that the tilde+quote 'trick' is really only something you have to take care of when trying to write unqualified symbol literals in a syntax-quote form (in order to bypass the reader's auto namespacing logic)

yuhan 2025-11-28T11:52:28.348549Z

if you're generating those arglist vectors programmatically there's no reason to deal with them - just splice the forms in directly

Jim Newton 2025-11-28T11:55:08.400479Z

ahh tilde+quote is to generate a var or to prevent a var from being generated?

weavejester 2025-11-28T21:20:34.316169Z

It's to prevent the symbol from being automatically qualified. For instance, (prn 1)` translates to '(clojure.core/prn 1) .

opqdonut 2025-11-28T11:45:04.341369Z

I played around with some quick-and-dirty parallelisation, and found ForkJoinPool to beat naive use of Futures. https://gist.github.com/opqdonut/9e95c4cbc19b0c927b8530428485bfe3

🎉 2
teodorlu 2025-11-28T11:47:27.696619Z

Futures start new threads for each (future ,,,) call.. If you can avoid the thread start overhead, you save time. Nice examples!

teodorlu 2025-11-28T11:49:11.221799Z

clojure.core.async/pipeline-blocking is another option. I wrote about https://play.teod.eu/clojure-easy-parallellism-with-pipeline-blocking/, but I have no idea whether it's faster or slower than your approach.

exitsandman 2025-11-28T11:49:55.959899Z

one thing to note is that you need to convey dynamic vars like future does

opqdonut 2025-11-28T11:50:39.427209Z

good point, yes, binding-conveyor-fn is needed

opqdonut 2025-11-28T11:52:22.396869Z

also, now that I'm reading the source of future-call, :once metadata on the fns might make sense

borkdude 2025-11-28T11:52:45.751299Z

clojure.core.reducers is based on this too

opqdonut 2025-11-28T11:53:04.079939Z

oh, indeed!

borkdude 2025-11-28T11:54:30.047279Z

I haven't seen it used much over the years

opqdonut 2025-11-28T11:54:54.157349Z

yeah it feels a bit like an unfinished idea

opqdonut 2025-11-28T11:55:06.162279Z

got shadowed by core.async and transducers

exitsandman 2025-11-28T11:57:06.564179Z

one thing I can't help but wonder is if future can benefit from virtual threads

opqdonut 2025-11-28T12:00:18.011999Z

I think you could use claypoole's future and Executors.newVirtualThreadPerTaskExecutor() already?

opqdonut 2025-11-28T12:00:42.402669Z

changing the clojure core agent-pool to virtual threads might break some existing code that assumes threads

borkdude 2025-11-28T12:00:59.799349Z

I'm sure that the core team is thinking about this. but first they are doing core.async + virtual threads. And they are hitting a problem that was introduced in JVM 24 -> 25 namely that virtual threads became GC roots so making lots of them can cause memory leaks

👀 1
exitsandman 2025-11-28T12:02:07.544689Z

isn't the whole point of vthreads that you aren't supposed to pool them?

opqdonut 2025-11-28T12:04:02.164669Z

I should probably benchmark vthread futures as well. I'm expecting them to be as fast as FJP

borkdude 2025-11-28T12:04:15.283859Z

in JVM 24 this wasn't the case, so they didn't hit that problem

Ovi Stoica 2025-11-28T12:04:17.112199Z

I believe this thread showcases the issue @borkdude mentioned above: https://clojurians.slack.com/archives/C05423W6H/p1760985798361189

borkdude 2025-11-28T12:05:02.505259Z

but in JVM 25 VTs became inspectable via some JVM monitoring UI and they made it GC roots so they didn't disappear when you inspected them.

borkdude 2025-11-28T12:05:26.539079Z

(I'm sure I get most of this wrong, but it's something in that direction)

borkdude 2025-11-28T12:07:35.726069Z

Ah @ovidiu.stoica1094 that's exactly the issue!

😎 1
exitsandman 2025-11-28T12:10:23.373249Z

I see

igrishaev 2025-11-28T13:10:42.535119Z

Sorry for being sceptical, but it looks like you mimic work with Thread/sleep. It doesn't reflect the real nature of parallel work. it would be better to replace sleep with downloading a file from a local HTTP server or calculate huge factorial

opqdonut 2025-12-01T07:29:47.278049Z

yeah, agreed. the beginning of the file is just a usage example, I used sleep there to keep the file self-contained. the benchmark does some summing of numbers, but it's not a very realistic case either!

opqdonut 2025-12-01T07:31:51.537439Z

I wonder if these days something like iterated SHA would be a good mock cpu workload.