Searching for Signal

the n01se blog

Clojure: Controlling run-away trains, onions, and exercise bikes.

A normal Clojure REPL (or prompt) in a terminal window is by default a bit touchy about infinite seqs, deeply nested structures, and long-running operations. Any of these can cause your REPL to wander off into the weeds, busy spinning, perhaps printing pages of useless data, with the only apparent remedy being to press CTRL-C, which generally kills the entire JVM, Clojure and all, and dumps you back at your operating system prompt.

It's unfortunate that this is the default, but Clojure's youth shows particularly when it come to tools and settings like this. Happily it's sufficiently mature to provide several solutions that are not difficult to apply.

Run-away trains

The most common of the problems listed above is the infinite seq. Such a seq is easy to create, easy to print, and can result in an run-away REPL. The solution: setting your *print-length* to something short of infinity. I recommend 103:

(set! *print-length* 103)

Now it's safe to print infinite sequences:

(iterate inc 0)
;=> (0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
    24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
    45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
    66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
    87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 ...)

Note the ... at the end of the list which indicates there was more to print, but Clojure is respecting our requested limit and giving up after 103 items. I've been mocked upon occasion for choosing 103, as if 102 is insufficient and 104 dangerous. Of course it's not so important exactly what you pick, so I'm happy to keep my rationale to myself.

Run-away onions

But *print-length* won't help you in the case of infinitely recursive structures, data nested inside data like layers of an onion. While less common, such nesting can be deep enough that it can still be a problem. To limit printing of recursive structures, use *print-level*. Usually 15 is about right for me:

(set! *print-level* 15)

Now it's safe to print infinitely recursive structures:

(let [x (atom 0)] (reset! x {:deeper x}))
;=> {:deeper #<Atom@4cb533b8: {:deeper #<Atom@4cb533b8:
    {:deeper #<Atom@4cb533b8: {:deeper #<Atom@4cb533b8:
    {:deeper #<Atom@4cb533b8: {:deeper #<Atom@4cb533b8:
    {:deeper #<Atom@4cb533b8: {:deeper #}>}>}>}>}>}>}>}

Again you get a little textual indicator that print is giving up, in this case it's just the # at the deepest level.

Run-away exercise bikes

But these settings only help take control of run-away printing. Sometimes the REPL gets hung up doing something other than printing, sitting there spinning away like a stationary bike, and neither of these settings will help you. My final advice is to use repl-utils to register your REPL thread as killable by CTRL-C:

(require 'clojure.contrib.repl-utils)

Now when you press CTRL-C, instead of shutting down the whole JVM, an exception will be thrown within the REPL thread, which is usually exactly what you need to halt run-away computation. Note that repl-utils does this using a deprecated Java API that is described as unsafe. And yet it remains useful for just such circumstances. For example:

(deref (promise))

Look at that -- instant deadlock! Now press CTRL-C, and you get:

java.lang.Exception: SIGINT (NO_SOURCE_FILE:0)

...and you're back to REPL prompt. But now you've violated Java, so I'd recommend saving what you need to save and then restart your JVM anyway. If you don't, weird things can happen. For example if you run the above deref-promise example again immediately, it may fail with a slightly different exception, even before you press CTRL-C. Similar strangeness can occur if you press CTRL-C at the REPL when no operation is running, though in that case it usually just kills your next expression, whatever it is.

Hopefully by setting your *print-length*, *print-level*, and break-thread, you can feel more comfortable at the REPL with less fear of getting stuck.
The Joy of Clojure
Thinking the Clojure Way

by Michael Fogus and Chris Houser