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.
In Clojure you just scan for & and drop the rest.
user=> (drop-while #(not= '& %) '[a b & {:keys [dude]}])
(& {:keys [dude]})yes chatgpt recommends drop-while . seems strange. but ok it’ll work.
why is this strange?
well it’s negative logic, and requires a predicate.
just that in CL its sort of a primitive and something one does often.
easy enough anyway, its a one-liner… no big deal of course.
in clojure most stuff is just data, no special primitives needed
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.
> 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.
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
Malli also has something similar in the malli.destructure ns, both approaches are pretty declarative
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.> 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.
I guess you mean the docstring generated by cider (and friends) or is it the docstring generated by clojure itself?
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
> 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.
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)
if you're generating those arglist vectors programmatically there's no reason to deal with them - just splice the forms in directly
ahh tilde+quote is to generate a var or to prevent a var from being generated?
It's to prevent the symbol from being automatically qualified. For instance, (prn 1)` translates to '(clojure.core/prn 1) .
I played around with some quick-and-dirty parallelisation, and found ForkJoinPool to beat naive use of Futures. https://gist.github.com/opqdonut/9e95c4cbc19b0c927b8530428485bfe3
Futures start new threads for each (future ,,,) call.. If you can avoid the thread start overhead, you save time.
Nice examples!
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.
one thing to note is that you need to convey dynamic vars like future does
good point, yes, binding-conveyor-fn is needed
also, now that I'm reading the source of future-call, :once metadata on the fns might make sense
clojure.core.reducers is based on this too
oh, indeed!
I haven't seen it used much over the years
yeah it feels a bit like an unfinished idea
got shadowed by core.async and transducers
one thing I can't help but wonder is if future can benefit from virtual threads
I think you could use claypoole's future and Executors.newVirtualThreadPerTaskExecutor() already?
changing the clojure core agent-pool to virtual threads might break some existing code that assumes threads
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
isn't the whole point of vthreads that you aren't supposed to pool them?
I should probably benchmark vthread futures as well. I'm expecting them to be as fast as FJP
in JVM 24 this wasn't the case, so they didn't hit that problem
I believe this thread showcases the issue @borkdude mentioned above: https://clojurians.slack.com/archives/C05423W6H/p1760985798361189
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.
(I'm sure I get most of this wrong, but it's something in that direction)
I see
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
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!
I wonder if these days something like iterated SHA would be a good mock cpu workload.