beginners 2025-07-30

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)))

✅ 1

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))

1

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

🙏 1

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

👍 1

I am trying to get this property:

(= (::key (meta (with-key #(+ 1 %))))
   (::key (meta (with-key #(+ 1 %)))))
;; => true

can 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. ❤️