clojure 2025-12-09

I guess I'll cause some more trouble, just for fun. Ran into this just now...

(vary-meta list assoc :original-symbol 'list)
; Execution error (UnsupportedOperationException) at user/eval53338 (REPL:1135).
; null
clj꞉user꞉> 
(class list)
clojure.lang.PersistentList$Primordial
Would a reasonable solution be something like this? Appears to only be an issue for list, not vector or other suspects.
(with-meta (fn [& args] (apply list args)) 
  {:original-symbol 'list})

Because of this: https://github.com/clojure/clojure/blob/master/src/clj/clojure/core.clj#L16-L20 It looks like all the other stuff is def'd to fn forms.

that's so interesting. i wonder if it's because it's a varargs function

ah! good catch Sean, that makes sense...

There are other variadic functions defined with fn forms below that so, no.

yes I believe Sean is correct, list is literally the first core def that Clojure defines, so it makes sense it's not a fn

hmm well, those are recursive, whereas list needs apply

clj꞉user꞉>  (class list)
clojure.lang.PersistentList$Primordial
clj꞉user꞉> (class vector)
clojure.core$vector
clj꞉user꞉> (class hash-map)
clojure.core$hash_map
clj꞉user꞉> (class (fn [] "hi"))
user$eval57217$fn__57218

looks like vector and hash-map pass the varargs seq to a .create method, whereas PersistentList$Primordial is a RestFn.

i just tried, and you can add a create(ISeq args) overload and change list to use it, and it works

-  list (. clojure.lang.PersistentList creator))
+  list (fn* ^:static list [& items] (. clojure.lang.PersistentList (create items))))
and
+public static IPersistentList create(ISeq args) {
+       if(args instanceof ArraySeq)
+       {
+               Object[] argsarray = ((ArraySeq) args).array;
+               IPersistentList ret = EMPTY;
+               for(int i = argsarray.length - 1; i >= ((ArraySeq)args).i; --i)
+                       ret = (IPersistentList) ret.cons(argsarray[i]);
+               return ret;
+       }
+       LinkedList list = new LinkedList();
+       for(ISeq s = RT.seq(args); s != null; s = s.next())
+               list.add(s.first());
+       return create(list);
+}

i wonder if there's performance implications to such a change

(december 2007, initial commit is sept 2007)

Thanks Noah. Appreciate the extra effort. Once I found my workaround, I was focused on my immediate task at hand.

But I think this is a very fitting topic for an ask.

We're in the ancient realms now, lol.

Alex Miller (Clojure team) 2025-12-09T19:12:07.578149Z

sorry, don't know the answer

👍 2

think there would be any interest in aligning list to use fn*?

Alex Miller (Clojure team) 2025-12-09T19:40:05.513899Z

I seriously doubt it :)

because "it hasn't been touched in 18 years, why touch it now?"

Alex Miller (Clojure team) 2025-12-09T19:42:43.305489Z

don't see that we are solving any problem here

the problem is "cannot treat list the same as other collection creation functions"

Alex Miller (Clojure team) 2025-12-09T20:26:30.905459Z

be more specific. you can treat it the same in many ways. in what ways is this difference preventing you from doing something you want to do?

Alex Miller (Clojure team) 2025-12-09T20:26:43.733599Z

what is your objective?

See the OP's Q about vary-meta not working on list 🙂

(I suspect there are other core vars you cannot call vary-meta on tho'... we just haven't tripped over them)

Alex Miller (Clojure team) 2025-12-09T20:29:17.453549Z

and why do you want to vary the meta of a core function?

Alex Miller (Clojure team) 2025-12-09T20:31:07.157229Z

what is the actual goal?

This is for his traceable execution project I think? @lambeauxworks

👍 1
Alex Miller (Clojure team) 2025-12-09T20:38:46.529989Z

you have to keep in mind where list is in bootstrapping - it's the very first function defined, there is nothing but special forms available. if you're taking varargs (as list does), you have no way to apply because apply is not yet defined

First off, this is not a deal breaker for me.

I have a way to achieve my goal without changes to core.

That being said, here's a data point for you. I'm going form by form and calling apply so I can aggregate all the intermediate values in a trace report. I'm storing the original symbol on a fn's meta so I can render a view that looks like source code.

Alex Miller (Clojure team) 2025-12-09T20:45:43.939979Z

yeah, I don't think there is any sort of requirement that a form has that

I think Noah's point is that there's an inconsistency, which I think is a valid concern, I'm just not blocked by it. For example:

clj꞉user꞉> (with-meta vector {:key "val"})
#function[clojure.lang.AFunction/1]
clj꞉user꞉> (with-meta hash-map {:key "val"})
#function[clojure.lang.AFunction/1]
clj꞉user꞉> (with-meta list {:key "val"})
; Execution error (UnsupportedOperationException) at user/eval68780 (REPL:1669).
; null

Strictly from a behavior standpoint, why is list exceptional, as shown by the above?

But from how this applies to what I'm doing, here's all I need to workaround this inconsistent behavior: https://github.com/Lambeaux/paper-trail/blob/main/src/lambeaux/paper_trail/impl/executor.clj#L243-L259

you have to keep in mind where list is in bootstrapping - it's the very first function defined, there is nothing but special forms available. if you're taking varargs (as list does), you have no way to apply because apply is not yet defined
That all seems fair to me, but is there a reason the "current" list can't be renamed to be list* and later in bootstrapping you just add list proper, once you have apply, etc, so it behaves like all the collection creators?

FYI:

user=> (->> (ns-publics 'clojure.core) (keep (fn [[sym var]] (when (ifn? @var) (try (vary-meta @var {:a 1}) nil (catch Exception _ sym))))))
(print-method print-dup unquote-splicing in-ns unquote load-file list)
Only list is fn?.

in-ns is clojure.lang.RT$1

Change it from ifn? to just fn? ...

clj꞉user꞉> 
(->> (ns-publics 'clojure.core) 
     (keep (fn [[sym var]] 
             (when (fn? @var) 
               (try (vary-meta @var {:a 1}) 
                    nil 
                    (catch Exception _ sym))))))
(list)

That was my note about only list being fn? 🙂

Oh facepalm didn't click from just the few words, my bad

@alexmiller yeah, i thought of that. i posted a diff earlier in the thread showing that you could have a IPersistentList create(ISeq args) method on PersistentList and change the impl to (fn* ^:static list [& items] (. clojure.lang.PersistentList (create items))) without relying on apply

i wonder if there are performance differences between the two implementations

Alex Miller (Clojure team) 2025-12-09T21:58:59.479929Z

I continue to not see a reason why this is important

1

🤷 if you don't see the inconsistency and error as important, then i guess that's that

Alex Miller (Clojure team) 2025-12-09T21:59:40.708939Z

and I think it's highly unlikely Rich would want to change things solely for "consistency"

Alex Miller (Clojure team) 2025-12-09T22:01:00.844379Z

of the universe of things we could spend time on, this seems so far down the list, I can't see it :)

☝️ 1

running quick-bench over the existing list and a list2 using my suggestion, list2 takes like 80% the time (550ns vs 360ns for 19 items)