3

When one is programming in an imperative programming languages such as Java one can conveniently add trace statements. For example:

for (int i=0; i<10; i++) {
  // do something
  // do something
  System.out.println("Some trace statement");
  // do something 
}

How does one accomplish this in a LISP dialect such as Clojure - for example say I wanted to add a trace just before recur:

(def fact
  (fn [n]
    (loop [cnt n acc 1]
       (if (zero? cnt)
            acc
          ;; say I want to add a trace here
          (recur (dec cnt) (* acc cnt))))))

Notes:

  1. The method should be relatively as simple as adding a line
  2. For example if I were to use a do block -- I have to reformat, make sure I close the brackets appropriately
user1172468
  • 5,306
  • 6
  • 35
  • 62
  • 1
    So you don't want to use `do` because of reformatting? What editor are you using? – Chiron Apr 07 '14 at 08:38
  • @Chiron, my point is that I know how to solve this issue using a do -- however I always feel it is more trouble than I'd like -- I did a lot of LISP in my youth but I've never found a convenient way to be able to add trace statements to my code. – user1172468 Apr 07 '14 at 08:43
  • @Chiron, these days I'm trying light table, but other editors are vi and eclipse. – user1172468 Apr 07 '14 at 08:43
  • @NielsK: just be realistic. If one can't write a `do` statement because there might be a bracket error, then don't program in Lisp. Has nothing to do with *supremacist* or *toxic*. It's just advice which saves him a lot of trouble. There are people on Stackoverflow asking fake questions like these. – Rainer Joswig Apr 07 '14 at 09:51
  • 2
    @NielsK That is not a valid argument. What about: "I don't want to add `if` condition to my Java code because I don't want to reformat my code" ? – Chiron Apr 07 '14 at 11:36
  • @NielsK git checkout . – Chiron Apr 07 '14 at 12:36
  • Although Spyscope looks like the promising answer, the OP might look into seeing if there's a paredit plugin for their editor. Paredit is all about making adding new statements to LISPs without introducing bracket errors – C. Warren Dale Apr 07 '14 at 18:37
  • 2
    @C.WarrenDale I edited my answer so that Paredit stands out a little more – coredump Apr 07 '14 at 20:49
  • 2
    @coredump Thanks! I didn't notice until after I'd made the comment, and I forgot to edit it because I am a poor netizen ;) – C. Warren Dale Apr 07 '14 at 20:51

4 Answers4

5

Non-invasive tracing

Lisp environments generally provide interactive debugging environment and trace mechanisms. For example, in SBCL, you could use the trace macro: you don't even need to modify your code, like you did in your Java example.

For Clojure, look at the tools.trace library, or the following answer: clojure: adding a debug trace to every function in a namespace?

Custom functions and macros

See also the many answers to this question: Debugging in Clojure? Most of them involve nesting the expression you want to debug/trace inside another expression, like Chiron suggested.

I don't think that "I have to reformat and close the brackets appropriately" is a good argument; everytime you edit your program you have to deal with the syntax, or else you won't ever modify your code.

Paredit

I personally don't use I am now a happy user of Paredit. Your editor keep track of parens and brackets while you code, which is quite handy.

Reader macros

I you really don't want to nest your expression inside another one, I suppose you could write a reader macro so that you could annotate an expression with a debug statement, but this is overkill, imho (edit: this is what spyscope does, apparently; see NielsK's answer).

Community
  • 1
  • 1
coredump
  • 37,664
  • 5
  • 43
  • 77
5

The Spyscope library provides a simple option for putting in trace prints without having to change the original syntax, just in the way you (and many others) prefer.

spyscope.repl=> (take 20 (repeat #spy/p (+ 1 2 3)))
6
(6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6) 

There's also ways of including trace messages

spyscope.repl=> #spy/d ^{:marker "triple-add"} (+ 1 2 3)
spyscope.repl$eval3935.invoke(NO_SOURCE_FILE:1) triple-add (+ 1 2 3) => 6
6

and even (partial) stack traces

spyscope.repl=> (take 20 (repeat #spy/d ^{:fs 3} (+ 1 2 3)))
----------------------------------------
clojure.lang.Compiler.eval(Compiler.java:6477)
clojure.lang.Compiler.eval(Compiler.java:6511)
spyscope.repl$eval675.invoke(REPL:13) (+ 1 2 3) => 6
(6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6)
NielsK
  • 6,886
  • 1
  • 24
  • 46
2

Use do block:

(def fact
(fn [n]
(loop [cnt n acc 1]
   (if (zero? cnt)
        acc
      (do
        (println "**")
        (recur (dec cnt) (* acc cnt)))))))

user=> (fact 4)
**
**
**
**
24

In your REPL:

(doc do)

do (do exprs*)
Special Form
Evaluates the expressions in order and returns the value of the last. If no expressions are supplied, returns nil. Please see http://clojure.org/special_forms#do

Chiron
  • 20,081
  • 17
  • 81
  • 133
  • Thank your for your Answer however I was looking for a more convenient form than adding a do block -- I'll update my question – user1172468 Apr 07 '14 at 08:21
  • 1
    For possible viewers. Original question didn't mention that the OP doesn't want to use `do` block. It was updated later. – Chiron Apr 07 '14 at 08:47
1

I have several macros that allow something like this (because it is handy not having to toss in a progn many times):

(my-trace ("hi mom" 1 2 3) recur (dec cnt) (* acc cnt))

my-trace simply expands into:

(progn (imperative-trace "hi mom" 1 2 3) (recur (dec cnt) (* acc cnt)))

a simpler variant is:

(echo hi-mom recur (dec cnt) (* acc cnt))

which captures the result of the wrapped form and prints it tagged with hi-mom.

A nicety is that I can insert/remove nil as the unevaluated first parameter to turn tracing off/on. On some debug out put I just leave the trace in place but disabled until the next time I need it.

-hk

kennytilton
  • 994
  • 1
  • 6
  • 16