Search code examples
debuggingcommon-lispbreakstack-frame

Why Slime's debugger is not evaluating this specific expression in the selected frame?


I am trying to learn Common Lisp with the book Common Lisp: A gentle introduction to Symbolic Computation. In addition, I am using SBCL, Emacs, and Slime.

By the end of chapter 10, the author discuss the useful break function. In order to provide a background context, he presents this problematic function:


(defun analyze-profit (price commission-rate)
  (let* ((commission (* price commission-rate))
         (result
           (cond ((> commission 100) 'rich)
                 ((< commission 100) 'poor))))
    (format t "~&I predict you will be: ~S"
            result)
    result))

The function works as expected being called in REPL with arguments such as:

> (analyze-profit 1600 0.15)
I predict you will be: RICH
RICH

> (analyze-profit 3100 0.02)
I predict you will be: POOR
POOR

However, it shows a wrong result when commission is exactly 100:

> (analyze-profit 2000 0.05)
I predict you will be: NIL
NIL

In order to debug it, the author inserts the break function on the definition:


(defun analyze-profit-debugging (price commission-rate)
  (let* ((commission (* price commission-rate))
         (result
           (cond ((> commission 100) 'rich)
                 ((< commission 100) 'poor))))
    (break "Value of RESULT is ~S" result)
    (format t "~&I predict you will be: ~S"
            result)
    result))

Then he examines the control stack while checking the value of local variables:

enter image description here

I know debuggers are notoriously implementation dependent.

In my environment (emacs, slime, sbcl) I was able to reproduce something similar following this great tutorial writthen by @Vindarel.

After putting the point (cursor) on stack 0: and pressing e:

Backtrace:
  0: (ANALYZE-PROFIT-DEBUGGING 2000 0.05)
  1: (SB-INT:SIMPLE-EVAL-IN-LEXENV (ANALYZE-PROFIT-DEBUGGING 2000 0.05) #<NULL-LEXENV>)
  2: (EVAL (ANALYZE-PROFIT-DEBUGGING 2000 0.05))

I am able to evalue the expression in the stack frame. Thus, I get this messages on the mini-buffer:

Eval in frame (COMMON-LISP-USER)> price
=> 2000 (11 bits, #x7D0, #o3720, #b11111010000)

And:

Eval in frame (COMMON-LISP-USER)> commission-rate
=> 0.05

Unfortunately, I cannot access the main problem which is the local variable named commission:

Eval in frame (COMMON-LISP-USER)> commission

I was expecting:

100.0

But I get an error message:

The variable COMMISSION is unbound. [Condition of type UNBOUND-VARIABLE]

I double-check the spelling. In addition, I also tried using p instead of e and moving the cursor around. However, nothing worked.

How can I check the value of the problematic variable commission?


Solution

  • If you navigate to the top frame in the debugger and press enter on that frame, you will see that commission is not known to the debugger as a local variable:

    Value of RESULT is NIL
       [Condition of type SIMPLE-CONDITION]
    
    Restarts:
     0: [CONTINUE] Return from BREAK.
     1: [RETRY] Retry SLIME REPL evaluation request.
     2: [*ABORT] Return to SLIME's top level.
     3: [ABORT] abort thread (#<THREAD "new-repl-thread" RUNNING {1002D65C93}>)
    
    Backtrace:
      0: (ANALYZE-PROFIT-DEBUGGING 2000 0.05)
          Locals:
            COMMISSION-RATE = 0.05
            PRICE = 2000
            RESULT = NIL
      1: (SB-INT:SIMPLE-EVAL-IN-LEXENV (ANALYZE-PROFIT-DEBUGGING 2000 0.05) #<NULL-LEXENV>)
      2: (EVAL (ANALYZE-PROFIT-DEBUGGING 2000 0.05))
     --more--
    

    The problem here is that the compiler has optimized away some debugging information. You can tell the compiler to include more debugging information with declaim at the beginning of the file, or declare in the function definition:

    (defun analyze-profit-debugging (price commission-rate)
      (declare (optimize (debug 3)))
      (let* ((commission (* price commission-rate))
             (result
               (cond ((> commission 100) 'rich)
                     ((< commission 100) 'poor))))
        (break "Value of RESULT is ~S" result)
        (format t "~&I predict you will be: ~S"
                result)
        result))
    

    The number given to debug can be 0, 1, 2, or 3; a larger number means more emphasis should be placed on preserving debugging information.

    Now in the debugger, pressing enter on the top stack frame shows the value of commission directly:

    Backtrace:
      0: (ANALYZE-PROFIT-DEBUGGING 2000 0.05)
          Locals:
            COMMISSION = 100.0
            COMMISSION-RATE = 0.05
            PRICE = 2000
            RESULT = NIL
      1: (SB-INT:SIMPLE-EVAL-IN-LEXENV (ANALYZE-PROFIT-DEBUGGING 2000 0.05) #<NULL-LEXENV>)
      2: (EVAL (ANALYZE-PROFIT-DEBUGGING 2000 0.05))
     --more--
    

    And, using the e command on the top stack frame now works as you initially expected:

    Eval in frame (COMMON-LISP-USER)> commission
    => 100.0