clojure-dev

2026-04-08T14:44:16.291859Z

hey ghadi, i was being cheeky in that thread, but i'm genuinely curious what the differences are between the two patches

ghadi 2026-04-08T14:44:35.811869Z

which two?

ghadi 2026-04-08T14:45:12.751719Z

(enabling socratic mode)

👍 1
🍿 3
2026-04-08T14:47:14.558789Z

i know that the pain point in constantly is usage in datomic (and that it's a much more popular function) which contributes to why it was evaluated/merged, but to me they seem to be equivalent changes

ghadi 2026-04-08T14:49:19.652429Z

when are the costs born for the (fixed) problem in constantly? when are the costs born for eduction?

seancorfield 2026-04-08T14:49:41.264129Z

For CLJ-2953, wouldn't removing the 0-arity be a breaking change for anyone dependent on older versions of those libraries that are (accidentally, incorrectly) using that arity?

Alex Miller (Clojure team) 2026-04-08T14:51:51.265149Z

I only found two actual uses of it, filed issues, both now removed

2026-04-08T14:53:11.813989Z

(up front: i am answering in good faith the socratic questions, not attempting to argue or nit pick)

2026-04-08T14:55:05.621969Z

The costs for constantly come when the returned function is invoked: (def foo (constantly 1)) (foo) (foo) produces two "costs". the costs for eduction are when the eduction is created. (def coll (eduction ...)) (vec coll) (vec coll) produces one "cost".

Alex Miller (Clojure team) 2026-04-08T14:58:53.047639Z

yeah, the function returned by constantly is often called A LOT but that is unusual for eduction

👍 1
2026-04-08T15:01:29.304299Z

removing the 0-arity is only a breaking change if you bind yourself to all observable behaviors, and not just documented behaviors. the core team approaches each such "breaking change" individually, evaluating usages and the cost/benefit of both. the only instances of (eduction) in public code were patched after alerting the two repos

ghadi 2026-04-08T15:16:15.221369Z

> The costs for constantly come when the returned function is invoked: (def foo (constantly 1)) (foo) (foo) produces two "costs". the costs for eduction are when the eduction is created. (def coll (eduction ...)) (vec coll) (vec coll) produces one "cost". correct. construction (1 time) vs use (N times)

ghadi 2026-04-08T15:17:37.288399Z

the N times behavior for eduction is already good - no problem to fix

ghadi 2026-04-08T15:17:49.750099Z

that was not true of constantly

2026-04-08T15:19:29.231469Z

the issue that olexander found (https://github.com/clj-kondo/clj-kondo/pull/2801/) is that in libraries like clj-kondo, eduction is the fastest method for iterating over certain sequences but the eduction creation still happens in a loop/hot path, because it's being called on multiple sequences in a row

2026-04-08T15:22:01.301999Z

improving the construction performance of eduction makes it better for all use cases (including nested usage), not just those that are "top level" or "outer loops", so to speak

Alex Miller (Clojure team) 2026-04-08T15:57:58.881629Z

I wanted to follow back on the use case where this came up and understand whether the time involved is actually relevant, I consider that an open question

2026-04-08T16:00:31.260559Z

sure, there's no rush and i don't expect y'all to merge the patch i wrote today or ever. i write the patches to satisfy myself; i know we have different priorities and different trade-off matrices.

2026-04-08T16:01:49.384829Z

but as an outsider, i am always curious about the core team's process, and find myself puzzled by certain decisions (wrt performance specifically)

Alex Miller (Clojure team) 2026-04-08T16:34:58.425949Z

like the linked PR changes multiple things and I don't see the eduction stuff showing up in the flamegraph. I believe the change is faster, I don't (yet) believe that it matters in the original context

✔️ 1
2026-04-08T17:00:35.616269Z

when i get some time, i'll run some benchmarks myself

2026-04-08T21:21:14.690959Z

> but the eduction creation still happens in a loop/hot path, because it's being called on multiple sequences in a row That's not the inner loop, which is the sequences.

2026-04-08T21:24:41.539469Z

The team are answering for my decision but I can tell you I thought the constantly ticket was fine right away and the eduction not, also right away. This had nothing to do with the code, but with the objective.

2026-04-08T21:26:33.794219Z

We're really not going to take on non-inner loop or I/O setup etc optimization without a compelling example of real world pain, because a priori I don't believe it will exist 🙂

2026-04-08T21:26:58.001689Z

and benchmarks are not real world pain

2026-04-08T21:35:17.950399Z

Everything can be made faster but not everything should, because time is finite and change is risk, optimized code is often more complex etc.

2026-04-08T21:41:36.561979Z

yes, i agree that not every piece of code should be optimized. that's why i chose to write a patch for eduction and not some (the other "optimized" function in the clj-kondo PR)

2026-04-08T21:49:06.865769Z

unrolling a varargs function to cover the most frequent cases is an existing idiom in clojure core (as seen by the constantly patch), and i try to think "if the code had been written the new way initially, would anyone suggest changing it to how it is now?"

2026-04-08T22:42:35.609979Z

Again, the varargs in eduction are not in/dominating the work, just the preamble. It's not a mechanical thing that varargs == bad