Fork me on GitHub
Carsten Behring21:10:41

Can I redefine "defn", so it stores the form past to it in metadata ?


another option is to use the existing meta to look up the function. There's a built in function clojure.repl/source, but it is a little inconsistent. I adapted source to work more consistently, which you can see at:

Carsten Behring21:10:38

yes, I did use that. My use case goes into "diffing" of clojure source code, but per function (instead of being file based) So I thought it could be interesting to look at the form which defined a function.


Yes you can do that if you want


But don't try to re-implement defn, just leverage the existing defn

Carsten Behring21:10:29

how could I do that ?

Carsten Behring21:10:18

without getting in a circle ?


are you diffing between versions in version control or redefined at the repl?

Carsten Behring22:10:38

hmmm. I want to track changes in the definition of functions over time. They are defined in source files. So the source-fn approach works in principle.

Carsten Behring22:10:53

Just a pitty that it gives me a String back.

Carsten Behring22:10:15

So I read somewhere te idea to change the defn macro, to store the form as metdata.


it's pretty easy to get a data structure from the string, you can just use read


or read-string

Carsten Behring22:10:43

Lets give it a try.


are you tracking changes while someone is working at the repl or some other way?


if you're trying to track changes at the repl, I might go the route of writing some repl middleware that receives each expression that will be eval 'd


if you're tracking changes in source files, I would use a tool like rewrite-clj that can parse clojure source into an AST

Carsten Behring22:10:16

the source code -> read-string is fine,., probbaly

Carsten Behring22:10:31

and the file parsing is as well a good idea.


also check out which has great static analysis tools that might be helpful


You can call the core one with full qualification: clojure.core/defn


(defmacro defn
  [& body]
       (clojure.core/defn [email protected])
      {:form '~&form})))


That will put the full source of the defn on the meta key named :form


You can try it with:

(defn foobar
  "This is a doc"
  {:some-attr :a1}
  ([] (foobar 1 2 3))
  ([a b c]
   {:pre [(number? a) (number? b) (number? c)]}
   (+ a b c))
  {:some-other-attribute :a2})

(:form (meta #'foobar))


It preserves all features of defn, and it should work even when AOT compiled I believe.

Carsten Behring21:10:09

I got a good start with doing this:

(defmacro defn* [x & body]
  (let [form `'~&form
        x    (vary-meta x assoc :form form)]
    `(def  ~x (fn  [email protected]))))
(intern 'clojure.core
        (with-meta 'defn {:macro true})

(defn sub [a b] (- a b))

 (meta #'sub))
;; => (defn sub [a b] (- a b))

Carsten Behring21:10:51

and it works for simple "defn". But require a more complex ns after this fails.

Carsten Behring21:10:38

probably my defn* macro is too simple to work with all types of defn invocations in real code.


To substitute it is a bit tricker. You need to grab the Var of the real defn and save it somewhere. Then ns-unmap from clojure.core defn, and then def inside clojure.core your new defn, and switch your defn to call the var you saved of the original. I think that should work, though I've never tried to substitute a core macro that also called the original, I only did it with functions which are a bit simpler cause you can swap the var to point to the new function.