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})For convenience if you want to source dive... https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/PersistentList.java#L24
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__57218looks 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
i suspect not, this is one of the oldest lines of code in clojure, from https://github.com/clojure/clojure/commit/a0d9ca6b9746767376ef8546889d7c44a9332ba5
(december 2007, initial commit is sept 2007)
I've written this up as an Ask: https://ask.clojure.org/index.php/14820/why-is-list-defined-with-a-restfn-instead-of-create-method, maybe @alexmiller or @richhickey know/remember the reason
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.
think there would be any interest in aligning list to use fn*?
I seriously doubt it :)
because "it hasn't been touched in 18 years, why touch it now?"
don't see that we are solving any problem here
the problem is "cannot treat list the same as other collection creation functions"
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?
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)
and why do you want to vary the meta of a core function?
what is the actual goal?
This is for his traceable execution project I think? @lambeauxworks
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.
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).
; nullStrictly 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 whereThat all seems fair to me, but is there a reason the "current"listis 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 toapplybecauseapplyis not yet defined
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
I continue to not see a reason why this is important
🤷 if you don't see the inconsistency and error as important, then i guess that's that
and I think it's highly unlikely Rich would want to change things solely for "consistency"
of the universe of things we could spend time on, this seems so far down the list, I can't see it :)
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)