3
votes

Let's say I have a program like this:

(define (foo x) 
  (local 
    ((define y (- x 1)))
    (* x y)))
(foo 3)

I want to be able to open a REPL between lines 3 and 4, such that I can explore (and possibly modify) the values of x and y by executing arbitrary statements.

To do this in Ruby, I would take the equivalent program:

def foo(x)   
  lambda {   
    y = x - 1
    x * y    
  }.call     
end       
puts (foo 3)

And modify it by adding a call to pry to give me a nicely-scoped repl where I want it:

require 'pry'
def foo(x)   
  lambda {   
    y = x - 1
    binding.pry
    x * y    
  }.call     
end       
puts (foo 3)

To do it in js, I would run this program under Firebug and just put a breakpoint on line 4:

foo = function(x) {  
  return (function(){
    var y = x - 1;   
    return x * y;    
  })();              
};                                    
console.log(foo(3)); 

And then I could explore stuff in the evaluation window.

Is there anything I can do to get this in Racket? The closest I've found is DrScheme's debugger, but that just presents all the values of the current scope, it doesn't let you explore them in a REPL as far as I can see.

2
I think this is a great question. Coming to Racket some years ago, I was flummoxed that hardly anyone used a debugger like I was religious about using with C++. It turned out not to matter so much. 1. I use an occasional log-debug or trace. 2. I play with little functions in the REPL, tweaking and observing them. 3. The functions are mostly "functional" (don't rely on reading or writing external state). With all those pieces, I've hardly ever wanted a traditional debugger again. Having said all that, something like a pry does sound intriguing.Greg Hendershott

2 Answers

2
votes

This isn't answering your original question, it's in response to your comment about making your own. I thought that was a really interesting idea so I explored it. What I was able to figure out:

Let's say you want this to work:

(define top-x 10)
(define (f)
  (for ([i 10])
    (displayln i)
    (when (= i 5)
      (pry)))) ; <= drop into a REPL here, resume after exiting REPL

A first attempt at pry:

(define (pry)
  (let loop ()
    (display "PRY> ")
    (define x (read))
    (unless (or (eof-object? x) (equal? x '(unquote exit)))
      (pretty-print (eval x))
      (loop))))

This seems to work:

> (f)
0
1
2
PRY> (+ 10 10)
20
PRY> ,exit
3
4
> 

But although it lets you access Racket functions like +, you can't access even your top-level variables like top-x:

> (f)
0
1
2
PRY> top-x
; top-x: undefined;
; cannot reference undefined identifier

You can get the top-level stuff by giving eval access to the current namespace, as explained here. So pry needs a namespace argument:

(define (pry ns)
  (let loop ()
    (display "PRY> ")
    (define x (read))
    (unless (or (eof-object? x) (equal? x '(unquote exit)))
      (pretty-print (eval x ns)) ; <---
      (loop))))

And to get that argument you need this incantation to your debugee file:

(define-namespace-anchor a)                  ; <---
(define ns (namespace-anchor->namespace a))  ; <---
(define top-x 10)
(define (f)
  (for ([i 5])
    (displayln i)
    (when (= i 2)
      (pry ns)))) ; <---

Now the REPL can see and change top-x:

> (f)
0
1
2
PRY> top-x
10
PRY> (set! top-x 20)
#<void>
PRY> top-x
20
PRY> ,exit
3
4
> 

Cool! But it can't change the local variable, i:

> (f)
0
1
2
PRY> i
; i: undefined;
; cannot reference an identifier before its definition

Shoot. The reason why is explained here.

You might imagine that even though eval cannot see the local bindings in broken-eval-formula, there must actually be a data structure mapping x to 2 and y to 3, and you would like a way to get that data structure. In fact, no such data structure exists; the compiler is free to replace every use of x with 2 at compile time, so that the local binding of x does not exist in any concrete sense at run-time. Even when variables cannot be eliminated by constant-folding, normally the names of the variables can be eliminated, and the data structures that hold local values do not resemble a mapping from names to values.

You might say, OK, but in that case...

How does DrRacket provide a debugger?

From what I was able to figure out, DrRacket does this by annotating the syntax before evaluating the program. From drracket/gui-debugger/annotator.rkt:

  ;; annotate-stx inserts annotations around each expression that introduces a
  ;; new scope: let, lambda, and function calls.  These annotations reify the
  ;; call stack, and allows to list the current variable in scope, look up
  ;; their value, as well as change their value.  The reified stack is accessed
  ;; via the CURRENT-CONTINUATION-MARKS using the key DEBUG-KEY

So I think that would be the jumping-off point if you wanted to tackle this.

1
votes

In the DrRacked IDE you have a DEBUG Q >| button. You can step through your program or you can do as you said in other languages, press right mouse button at the expression you want to investigate and either choose continue to this point for once only or pause at this point for a breakpoint, then press GO > to run the program.

To inspect or change x, put you mouse pointer over it and use right mouse button. To change you choose (set! x ...) in the menu.

As for the in language repl, You could make your own (pry) to start a repl in there and in Common Lisp you could have just signaled an error to get to the nice debugger.