I am trying to cache things with anonymous functions in them. I have a macro that adds metadata of pr-str'd form onto the metadata of it's argument, and it works great for (fn [...] ...) or something like (constantly true). BUT, it doesn't work with reader-macro-anon functions like #(inc %) because they are evaled their args are gensym'd before being passed into the macro, and therefore are not stable. I am thinking that I should either make them illegal with a kondo linting rule, or something.
Maybe I can identify them inside of with-key by checking for the #"(fn* [" regex, which seems brittle but seems to work for everything tried
(defmacro with-key
[body]
`(try (with-meta ~body
(assoc (meta ~body) ::key ~(pr-str body)))
(catch Exception _# ~body)))by definition nothing is "evaled before being passed into the macro"
#(inc %) is transformed by the reader into something like the equivalent of reading (fn [a] (inc a))
the reader does that kind of transformation in a number of places like 'a is read as the same as (quote a) , @a => (deref a) etc
user=> (read-string "#()")
(fn* [] ())
user=> (read-string "#(inc %)")
(fn* [p1__7#] (inc p1__7#))
user=> (read-string "'a")
(quote a)
user=> (read-string "@a")
(clojure.core/deref a)
user=>
Thanks for your reply. Here's what I am seeing:
(::key (meta (with-key (fn [x] (+ 1 x)))))
;; => "(fn [x] (+ 1 x))"
(::key (meta (with-key #(+ 1 %))))
;; => "(fn* [p1__236456#] (+ 1 p1__236456#))"so I guess what I mean is that the reader version of anon fn puts some gensym'd names in there, which makes the form unstable, and hence difficult to cache with, maybe I can walk it and re-stabilize the arg symbols?
if what you care about is the unread form, then use that as the key
I am trying to get this property:
(= (::key (meta (with-key #(+ 1 %))))
(::key (meta (with-key #(+ 1 %)))))
;; => truecan I access the unread form of #(+ 1 %)?
no, you have to save it before you send it to the reader
user=> (deftype Foo []) user.Foo user=> (read-string "#user.Foo []") #object[user.Foo 0x71178a52 "user.Foo@71178a52"] user=>
the reader can produce arbitrary objects, not all of which are printable
I'm trying to understand: > if what you care about is the unread form, then use that as the key what would I use as the key?
so there is some characters "#(inc %)" the reader reads those from somewhere and turns it into the form (data structure) (fn* [p1__7#] (inc p1__7#))
so get those sequence of characters from where ever before they go to the reader
these function-containing-things are embedded in our source code, and i am calling with-key from inside some frameworkey macros so I dont see how that would work
it isn't, it won't, post reader you cannot expect the pr str to be stable outside a limited set of cases, of which #(inc %) falls outside of
ok. I think the workaround I'll use is to un-gensym the argument list while it is a list:
meaning walk this form, and replace p1__236595# with a and so on
(freeze-body #(+ %1 %2 %&))
;;=> (fn* [p1__236595# p2__236596# & rest__236597#] (+ p1__236595# p2__236596# rest__236597#))Thank you Kevin. ❤️