1

I'm trying to write a debug macro that prints out expressions and their values. This lead to problems if I send in a lazy-seq, because if I turn it into a string (with str) the program hangs. It's easy to detect a lazy-seq if it's at the toplevel:

(def foo (cycle [1 2]))
(= (type foo) clojure.lang.LazySeq) ;=> true

But of course if it's nested inside another collection this doesn't work

(def bar (list (cycle [1 2])))
(= (type bar) clojure.lang.LazySeq) ;=> false

To deal with this I would need one of two things:

1: A function which checks a collection too see if it contains a lazy-seq nested somewhere.

2: A function to turn a collection into a string without evaluating nested lazy-seqs, something like this:

(str2 {:inf (cycle [1 2])}) => "{:inf #clojure.lang.LazySeq@e9383}"

Using Michał Marczyk's answer I came up with this macro:

(defmacro dbg-print [& rest]
  "Print out values or expressions in context"
  `(let [lazy-take# 5 ;when printing lazy-seq, how many elements to print
         symb-str# (map str '~rest)
         symb-evl# (reverse
                    (binding [*print-length* 10] 
                      (loop [coll# (list ~@rest) retur# '()]
                        (if (not (empty? coll#))
                          (recur (rest coll#) (cons (pr-str (first coll#)) retur#))
                          retur#))))
         pairs# (map #(str %1 %2 %3 %4) symb-str# (repeat ":") symb-evl# (repeat " "))
         str# (reduce str pairs#)]
     (println (format "%s\n" str#))))

It works like this:

(dbg-print (+ 1 3) (cycle [1 2])) ;=> (+ 1 3):4 (cycle [1 2]):(1 2 1 2 1 2 1 2 1 2 ...) 

And can handle nested lazy-seqs:

(dbg-print (list (cycle [1 2]))) ;=> (list (cycle [1 2])):((1 2 1 2 1 2 1 2 1 2 ...)) 
2
  • A practical counter-question: Why would you ever want to debug print a lazy-seq that is infinite? If you don't know about the infinity, it probably is the bug. If you do know about the infinity the more interesting part to debug is where you take something from it (sth. like (dbg-macro (first (partition-by (comp = second) inf-seq)))) . Just saying that a limit like your lazy-take# 5 would worry me more about generating bugs in my thinking while I do the debugging because the debug output would not represent the data that are processed in the program. Commented Jun 13, 2013 at 22:39
  • Well you can figure it what it contains by taking something from it, but it's nice to get an immediate overview of the structure. For example I had a lazy-seq that was accidentally wrapped in one list to much. If I take the first of that list, the program will crash. It's nice in general not to have the macro crash because you accidentally fed it a lazy-seq.
    – snowape
    Commented Jun 14, 2013 at 9:47

2 Answers 2

4

You can use the built-in Vars *print-length* and *print-level* with the pr / print family functions (including pr-str if you want to get a string representation back as a return value, rather than have it printed out):

(binding [*print-length* 3
          *print-level*  3]
  (prn ((fn explode []
          (repeatedly #(repeatedly explode))))))

This prints out

(((# # # ...) (# # # ...) (# # # ...) ...) ((# # # ...) (# # # ...) (# # # ...) ...) ((# # # ...) (# # # ...) (# # # ...) ...) ...)

where the #s indicate that those parts of the data structure are omitted due to descending past *print-level* and the ...s indicate that those parts of the data structure are omitted due to extending past *print-length*.

Two further examples with some actual data printed:

user> (binding [*print-length* 10]
        (prn (cycle [1 2 3])))
(1 2 3 1 2 3 1 2 3 1 ...)

user> (binding [*print-level* 10]
        (prn ((fn step [i]
                (lazy-seq (list i (step (inc i)))))
              0)))
(0 (1 (2 (3 (4 (5 (6 (7 (8 (9 #))))))))))

Finally, an example with a returned string:

user> (binding [*print-length* 2
                *print-level*  2]
        (prn-str ((fn explode []
                    (repeatedly #(repeatedly explode))))))
"((# # ...) (# # ...) ...)\n"

This functionality is documented in the docstrings for the Vars in question, see (doc *print-length*) and (doc *print-level*).

3

A couple approaches come to mind:

  • Walk the structure with prewalk and replace the lazy sequences with a place holder.
  • walk the sequence with a prewalk and apply take 10 to each collection recursively.
  • extend the print-method multi method for the Clojure.lang.LazySeq to print them however you chose.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.