beginners

escherize 2025-07-30T22:06:32.405569Z

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
2025-07-30T22:21:34.727949Z

by definition nothing is "evaled before being passed into the macro"

2025-07-30T22:22:33.534509Z

#(inc %) is transformed by the reader into something like the equivalent of reading (fn [a] (inc a))

1
2025-07-30T22:23:58.716899Z

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
2025-07-30T22:30:41.606289Z

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

escherize 2025-07-30T22:31:23.387149Z

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

escherize 2025-07-30T22:32:34.595189Z

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?

2025-07-30T22:33:29.509239Z

if what you care about is the unread form, then use that as the key

👍 1
escherize 2025-07-30T22:34:31.683459Z

I am trying to get this property:

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

escherize 2025-07-30T22:34:53.297469Z

can I access the unread form of #(+ 1 %)?

2025-07-30T22:35:20.227039Z

no, you have to save it before you send it to the reader

2025-07-30T22:35:27.602429Z

user=> (deftype Foo []) user.Foo user=> (read-string "#user.Foo []") #object[user.Foo 0x71178a52 "user.Foo@71178a52"] user=>

2025-07-30T22:35:46.432709Z

the reader can produce arbitrary objects, not all of which are printable

escherize 2025-07-30T22:36:27.346379Z

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?

2025-07-30T22:37:40.915499Z

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

2025-07-30T22:38:12.142749Z

so get those sequence of characters from where ever before they go to the reader

escherize 2025-07-30T22:39:37.602239Z

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

2025-07-30T22:40:55.772419Z

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

escherize 2025-07-30T22:44:14.872269Z

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

escherize 2025-07-30T22:48:37.602009Z

Thank you Kevin. ❤️