Skip to content

Instantly share code, notes, and snippets.

@jackfirth
Last active November 13, 2015 18:48
Show Gist options
  • Save jackfirth/fe5259f953735202296c to your computer and use it in GitHub Desktop.
Save jackfirth/fe5259f953735202296c to your computer and use it in GitHub Desktop.

12 Days of Racket Packages - debug

raco pkg install debug

It's the simplest tools that we reach for first. That's why one of the most common ways programmers debug their programs is with printf, display, print, console.log, System.out.println, System.Console.WriteLine, or whatever other console printing tool your language of choice has. It's easy to understand, not too inflexible, and usually good enough. But even for only simple debugging, is that the best a language can do?

Not in Racket! The debug package (source) by Alex Knauth is a unique approach to debugging. Instead of manually writing print statements and formatting strings in the middle of the flow of your code, debug only requires two characters:

#lang debug racket
(* #R(+ 1 2 3) (- 10 5))

Those two characters are #R; using #lang debug racket adds some magic to the Racket where any expressions that look like #R(...) have their result printed to current-error-port (which defaults to stderr) when evaluated. The above program produces this error output:

(+ 1 2 3) = 6

There are a few important details to note. Firstly, the #R(...) expression only captures what the source code looks like and the final output value, it does not display what the inputs evaluated too. This is in general difficult to do because it's not easy to tell if the surrounded expression is a macro or a function application. As an example, when used like this:

(define some-var 1)
(* #R(+ some-var 2 3) (- 10 5))

The error output is (+ some-var 2 3) = 6. The information that some-var evaluated to 1 is not available for debugging.

It's also somewhat verbose to log a non-expression, for instance a variable. To log only some-var in the previous example, values must be used:

(define some-var 1)
(* (+ #R(values some-var) 2 3) (- 10 5))

All that said, this is still easier than inserting printf statements everywhere, as statements are tricker to interleave into complex expressions. You'd have to pull out the expression you want to log and name it with a let or define before printing it.

debug is an example of a meta language. Rather than being its own #lang debug language, it's defined as a language that re-uses all the definitions and syntax of some base language and adds its own special reader extensions. You can just as easily use debug with typed/racket code:

#lang debug typed/racket
(* #R(+ 1 2 3) (- 10 5))

Meta languages are powerful tools, and are best suited for specifying "this adds some custom syntax for reading the source file but doesn't change anything else". Another example of a meta language is at-exp, which adds Scribble's @-expressions to the underlying language:

#lang at-exp racket
@+[1 2 3]
@displayln{Hello world!}

Most meta languages can be freely mixed with each other:

#lang debug at-exp racket
@+[1 #R@*[10 5]]

But back to debug. There's a few other features available as well. If you use #RR instead of just #R, the output includes a line number:

#lang debug racket
(+ 1 #RR(* 10 5))

output:

(* 10 5) = 50 on line 2
51

Adding another R to get #RRR includes the filename as well:

#lang debug racket
(+ 1 #RRR(* 10 5))

output:

(* 10 5) = 50 on line 2 in "/.../debug-example.rkt"
51

Additionally, since all logs are sent to current-error-port, you can change where they're sent by parameterizing the current error port. This lets you, for example, write all these logs to disk or send them over a network. But 99% of the time, all you need is a better printf. For more complex debugging, there are some more featureful choices available. The medic package lets you instrument much more comprehensive full-program debugging overlays, while the unstable/debug library that ships with standard Racket includes some debug forms that operate similarly to this package with more verbosity.

So, next time you're tempted to debug with printf, go meta and add debug to your language!

@jackfirth
Copy link
Author

Note: needs exposition on when more sophisticated debugging tools like Medic are appropriate

@samth
Copy link

samth commented Nov 12, 2015

  • I think the install line should go at the top.
  • Maybe say what current-error-port is by default when first mentioned.

@jackfirth
Copy link
Author

Needs note about unstable/debug

@SuzanneSoy
Copy link

I didn't know about this package, it's nice to see that it works well with scribble/lp2 too:

#lang debug scribble/lp2

@(require racket/base)
@(let () #RR(string-append "Hello " " World")) ;; Displayed when run with scribble

@chunk[<*> (displayln #RR(+ 2 3))] ;; Displayed when run as a program

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment