How to print a source of a function passed as a parameter

Let’s say I have a function that takes another function as an argument. I’d like to print the source of the function passed. i.e.:

(defn foo [f]
  (print
  ;; need to know the source of 'f' here
  ))

Is that possible? I tried using clojure.repl/source and clojure.repl/source-fn and clojure.core/meta but I can’t seem to find a way.

Not possible if you pass in just the regular function since that will be a Function instance. You can get the JS source but I presume you want the CLJS source?

You could modify the place where you call it to pass addtional information so you can eventually get the source info but a regular passed function does not have the required info to get the source.

In this specific case I need to know the source of Clojure function, I suppose whatever solution is out there, it would be very different for Clojurescript.

It’s totally hacky but something like this could work:

(defn var-source
  "This is almost an exact copy of `clojure.repl/source-fn` but
  accepting var instead of symbol in current ns (*ns*)."
  [v]
  (when-let [filepath (:file (meta v))]
    (when-let [strm (.getResourceAsStream (clojure.lang.RT/baseLoader) filepath)]
      (with-open [rdr (java.io.LineNumberReader. (java.io.InputStreamReader. strm))]
        (dotimes [_ (dec (:line (meta v)))] (.readLine rdr))
        (let [text (StringBuilder.)
              pbr (proxy [java.io.PushbackReader] [rdr]
                    (read [] (let [i (proxy-super read)]
                               (.append text (char i))
                               i)))
              read-opts (if (.endsWith ^String filepath "cljc") {:read-cond :allow} {})]
          (if (= :unknown *read-eval*)
            (throw (IllegalStateException. "Unable to read source while *read-eval* is :unknown."))
            (read read-opts (java.io.PushbackReader. pbr)))
          (str text))))))


(defn fn-source
  [f]
  (let [[ns-name sym-name]
        (-> (str f)
            (clojure.repl/demunge)
            (clojure.string/replace #"@.*$" "")
            (clojure.string/split #"/"))
        ns (the-ns (symbol ns-name))
        fn-var (ns-resolve ns (symbol sym-name))]
    (var-source fn-var)))

(comment
  (fn-source tap>)
;; => "(defn tap>\n  \"sends x to any taps. Will not block. Returns true if there was room in the queue,\n  false if not (dropped).\"\n  {:added \"1.10\"}\n  [x]\n  (force tap-loop)\n  (.offer tapq (if (nil? x) ::tap-nil x)))"
  )

1 Like
(require '[clojure.repl :refer [demunge source-fn]])

(defn fn-source [f]
  (let [fn-name (-> f .getClass .getName)
        fn-name (demunge fn-name)
        fn-sym (symbol fn-name)]
    (println (source-fn fn-sym))))

(fn-source +)
;; =>
;; (defn +
;;   "Returns the sum of nums. (+) returns 0. Does not auto-promote
;;   longs, will throw on overflow. See also: +'"
;;   {:inline (nary-inline 'add 'unchecked_add)
;;    :inline-arities >1?
;;    :added "1.2"}
;;   ([] 0)
;;   ([x] (cast Number x))
;;   ([x y] (. clojure.lang.Numbers (add x y)))
;;   ([x y & more]
;;    (reduce1 + (+ x y) more)))
8 Likes

@bartuka That code doesn’t fit the requirements:

(require '[clojure.repl :refer [source-fn]])

(defmacro sym->source [sym]
  (println (source-fn sym)))

(defn foo [f]
  (sym->source f))

(foo +) ;;=> nil
1 Like

Oh, that’s true. It works only by passing the f directly. I will delete the answer as it is documented in your response as well. thanks @borkdude !!

For those wondering, as I did, what the difference between clojure.repl/source and clojure.repl/source-fn is, source is a macro that prints the source code of a given function directly to standard out, and actually uses source-fn, whereas source-fn is a function that returns a string representation of the given function.

Both require that the function be given as a symbol that resolves to a Var defined in a namespace for which the .clj is in the classpath. This is why it is necessary to first get the name of the function, demunge it (e.g. (clojure.repl/demunge "clojure.core$_PLUS_") => "clojure.core/+"), and then convert it to a symbol before passing it as an argument as @borkdude showed in his answer.

It works for named functions. But there’s no way to get the source of inline (lambda) function, right?

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.