2
\$\begingroup\$

I have implemented a function, which logs the inputs and outputs of any function, indenting the log based on the depth of the call stack:

(def depth (atom 0))

(defn logfun [fun]
       (fn [& args]
           (do
               (swap! depth inc)
               (println (str (apply str (repeat @depth " ")) ">>> (" fun " " args ")"))
               (let [result (apply fun args)]
                    (do 
                         (swap! depth dec)
                         (println (str (apply str (repeat @depth " ")) " <<< " result))
                         result)))))

Usage example:

(defn fact [n] (if (> n 1) (* n (fact (dec n))) n))
(def fact (logfun fact))
(fact 5)

Output:

 >>> (lang.core$fact@5eb14bce (5))
  >>> (lang.core$fact@5eb14bce (4))
   >>> (lang.core$fact@5eb14bce (3))
    >>> (lang.core$fact@5eb14bce (2))
     >>> (lang.core$fact@5eb14bce (1))
     <<< 1
    <<< 2
   <<< 6
  <<< 24
 <<< 120

Main questions:

  • Is there a way to improve this function so that it is not dependent on global variables? (I have already attempted a solution based on with-local-vars / var-set -- see below, but it seems to me that they do not work as I wish they did).

  • Are there any edge cases, for which the above function does not work?

  • Are there any modifications by which this function can be made better adhering to Clojure best practices (besides somehow getting rid of the global state)?

  • Is there a way to improve the output of the function names (e.g., instead of lang.core$fact@5eb14bce I would like to get lang) in a way that satisfies both conditions below:

    1. It must be available in the Clojure core language (no third-party libs, no usage of repl utilities)
    2. It must not use string manipulation (e.g. find the @ and $ characters via regexp and then strip away the parts before/after them).

In other words, I'm looking for something like: (get-pretty-printed-function-name fun). (I already did some research, and found only solutions which do not satisfy the criteria above. Still asking, just in case ;) )

The attempted, NOT WORKING solution using var-set/with-local-vars:

(defn logfun [fun]
   (with-local-vars [depth 0]
       (fn [& args]
           (do
               (var-set [depth (inc @depth)])
               (println (str (apply str (repeat @depth " ")) ">>> (" fun " " args ")"))
               (let [result (apply fun args)]
                    (do 
                         (println (str (apply str (repeat @depth " ")) " <<< " result))
                         (var-set [depth (dec @depth)])
                         result))))))

EDIT: using bound-fn, I get one step further, but unluckily it still does not do what is expected, as it seems that the variable depth in the closure is reset to zero upon every invocation of the returned function:

(defn logfun [fun]
   (with-local-vars [depth 0]
       (bound-fn [& args]
           (do
               (var-set depth (inc @depth))
               (println (str (apply str (repeat @depth " ")) ">>> (" fun " " args ")"))
               (let [result (apply fun args)]
                    (do 
                         (var-set depth (dec @depth))
                         (println (str (apply str (repeat @depth " ")) " <<< " result))
                         result))))))

Result:

 >>> (lang.core$fact@510da75f (5))
 >>> (lang.core$fact@510da75f (4))
 >>> (lang.core$fact@510da75f (3))
 >>> (lang.core$fact@510da75f (2))
 >>> (lang.core$fact@510da75f (1))
 <<< 1
 <<< 2
 <<< 6
 <<< 24
 <<< 120
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.