4
votes

What is the idiomatic way of printing values inside a let binding ?

When I started developing in Clojure, I wrote code in the REPL, that I then turned into simple let expressions. Being a beginner, I often made mistakes during this (simple) transformation phase.

(let [a (aFn ...)
       b (bFn ... a)]
   ;; error above
)

So I would transform it back to something like that, basically inlining things :

(println "a is"    (aFn ...))
(println "b is" (bFn ... (aFn ...)))
(let [a (aFn ...)
       b (bFn ... a)]
   ;; ...
)

It works most of the time thanks to Clojure being nice (immutability, referential transparency..).

Now I do something along the lines of :

(let [a (aFn ...)
       _ (println "a is" a)
       b (bFn ... a)
      _ (println "b is" b)]
   ;; ...
)

It is an improvement, but it still feels clumsy. What is the proper way to do this ?

2
What you really want is a debugger. If you use cursive you have one. Though, what you can do is spy local bindings: brownsofa.org/blog/2014/08/03/debugging-in-clojure-tools - ClojureMostly
@Andre you're probably right. Is feels stupid to prototype and make it work in the REPL, then having to debug it again so I thought there could be something else. - nha
I do this all the time but it doesn't bother me unless it goes into production. Using print statements to do debugging is already clumsy, and the extra underscore required inside of let makes little difference IMHO. - Alex

2 Answers

3
votes

I tend to take a totally different approach. I never put print statements in my let bindings. I also think you need to be careful about calling a function just to get the value for debugging purposes. While we would like all our functions to be side-effect free, this is not always the case, so calling the funciton just to get a value to print may have unexpected results. There is also the issue of how printing values can impact on laziness and realising of lazy sequences etc.

My approach is to define some debugging functions, which I stick in a 'debug' namespace. I then call these debug functions when needed from inside the body of the function - not in the let binding section. Often, I also define a debug-level var so that I can have some control over debugging verbosity. this allows me to change one var and increase or decrease the amount of information logged/printed.

I've experimented with 'clever' macros to make debugging easier - but to be honest, these usually take more effort to get right than the benefit they provide.

I like having my debug functions in a separate namespace as this helps me ensure I've not left any debugging code in my production version - or it allows me to leave debug statements in there, but have them 'do nothing' by setting an appropriate debug level.

As mentioned by another post, using a debugger can eliminate/reduce the need to have these print statements or debug functions. However, I think debuggers can be a double edged sword as well. Too often, people get into bad debugging hapits where they rely on trace and inspect rather than thinking about and analysing exactly what is going on. This can tend to development driven by too much trial and error and not enough analysis and understanding.

You could start with something as simple as

(def debug-level 20)

(defn debug [lvl prefix val]
  (if (>= lvl debug-level)
    (println (str prefix ": " val)))

(defn debug1 [prefix v]
  (debug 10 prefix v))

(defn debug2 [prefix v]
  (debug 20 prefix v))

etc

and then just call

(debug2 :a a)

in the body of your function to have the value of a printed when debug-level is 20 or higher.

2
votes

You could define a print function that returns its argument:

(defn cl-print [x] (doto x (print)))

Then, it is only a matter of wrapping your expressions:

(let [a (cl-print (aFn ...))
      b (cl-print (bFn ... a))]
   ...)