Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can you get the "code as data" of a loaded function in Clojure?

To put it another, way, "Okay, so code is data..."

That thread addresses how to read from a source file, but I'm wondering how to get the s-expression of an already-loaded function into a data structure that I can read and manipulate.

In other words, if I say,

(defn example [a b] (+ a b))

can't I get that list at runtime? Isn't this the whole point of "code as data"?

This is really a general Lisp question, but I'm looking for an answer in Clojure.

like image 611
harpo Avatar asked Feb 09 '12 20:02

harpo


2 Answers

You can use the clojure.repl/source macro to get the source of a symbol:

user> (source max)
(defn max
  "Returns the greatest of the nums."
  {:added "1.0"
   :inline-arities >1?
   :inline (nary-inline 'max)}
  ([x] x)
  ([x y] (. clojure.lang.Numbers (max x y)))
  ([x y & more]
   (reduce1 max (max x y) more)))
nil

But this is only part of the answer. AFAICT source looks up the source filename and line number that define the given symbol, and then prints the source code from the file. Therefore, source will not work on symbols that you do not have the source for, i.e. AOT-compiled clojure code.

Coming back to your original question, you can think of source as reading the meta data associated with the given symbol and simply printing that. I.e. it's cheating. It's not in any way returning "code as data" to you, where with code I mean a compiled clojure function.

In my mind "code as data" refers to the feature of lisps where source code is effectively a lisp data structure, and therefore it can be read by the lisp reader. That is, I can create a data structure that is valid lisp code, and eval that.

For example:

user=> (eval '(+ 1 1))
2

Here '(+ 1 1) is a literal list which gets read by the clojure reader and then evaluated as clojure code.

Update: Yehonathan Sharvit was asking in one of the comments if it's possible to modify the code for a function. The following snippet reads in the source for a function, modifies the resulting data structure, and finally evaluates the data structure resulting in a new function, my-nth, being defined:

(eval
 (let [src (read-string (str (source-fn 'clojure.core/nth) "\n"))]
   `(~(first src) my-nth ~@(nnext src))))

The syntax-quote line replaces nth with my-nth in the defn form.

like image 148
liwp Avatar answered Nov 09 '22 05:11

liwp


You can get the source in recent versions of clojure with the source function.

user=> (source nth)
(defn nth
  "Returns the value at the index. get returns nil if index out of
  bounds, nth throws an exception unless not-found is supplied.  nth
  also works for strings, Java arrays, regex Matchers and Lists, and,
  in O(n) time, for sequences."
  {:inline (fn  [c i & nf] `(. clojure.lang.RT (nth ~c ~i ~@nf)))
   :inline-arities #{2 3}
   :added "1.0"}
  ([coll index] (. clojure.lang.RT (nth coll index)))
  ([coll index not-found] (. clojure.lang.RT (nth coll index not-found))))
nil

to get the string as a value you can wrap this in with-out-str:

user=> (with-out-str (source nth))
"(defn nth\n  \"Returns the value at the index. get returns nil if index out of\n  bounds, nth throws an exception unless not-found is supplied.  nth\n  also works for strings, Java arrays, regex Matchers and Lists, and,\n  in O(n) time, for sequences.\"\n  {:inline (fn  [c i & nf] `(. clojure.lang.RT (nth ~c ~i ~@nf)))\n   :inline-arities #{2 3}\n   :added \"1.0\"}\n  ([coll index] (. clojure.lang.RT (nth coll index)))\n  ([coll index not-found] (. clojure.lang.RT (nth coll index not-found))))\n"
user=> 
like image 34
Arthur Ulfeldt Avatar answered Nov 09 '22 06:11

Arthur Ulfeldt