Hi everyone,

I have a question about the implementation of errortrace.

Consider the classic factorial program, except that the base case is buggy:

(define (fact m)
  (let loop ([n m])
    (cond
      [(zero? n) (/ 1 0)]
      [else (* (loop (sub1 n)) n)])))

(fact 5)

Running this program with racket -l errortrace -t fact.rkt gives the
following output:

/: division by zero
  errortrace...:
   /Users/sorawee/playground/fact.rkt:9:17: (/ 1 0)
   /Users/sorawee/playground/fact.rkt:10:12: (* (loop (sub1 n)) n)
   /Users/sorawee/playground/fact.rkt:10:12: (* (loop (sub1 n)) n)
   /Users/sorawee/playground/fact.rkt:10:12: (* (loop (sub1 n)) n)
   /Users/sorawee/playground/fact.rkt:10:12: (* (loop (sub1 n)) n)
   /Users/sorawee/playground/fact.rkt:10:12: (* (loop (sub1 n)) n)

I find this result subpar: it doesn’t indicate which call at the top-level
leads to the error. You can imagine another implementation of fact that
errors iff m = 5. Being able to see that (fact 5) at the top-level causes
the error, as opposed to (fact 3), would be very helpful.

Not only that, (* (loop (sub1 n)) n) also looks weird. There’s nothing
wrong with multiplication, so I don’t find this information useful.

The tail-recursive factorial is similarly not helpful:

(define (fact m)
  (let loop ([n m] [acc 1])
    (cond
      [(zero? n) (/ 1 0)]
      [else (loop (sub1 n) (* n acc))])))

(fact 5)

produces:

/: division by zero
  errortrace...:
   /Users/sorawee/playground/fact.rkt:9:17: (/ 1 0)

------------------------------

I have been toying with another way to instrument the code. It roughly
expands to:

(define-syntax-rule (wrap f)
  (call-with-immediate-continuation-mark
   'errortrace-k
   (λ (k)
     (let ([ff (thunk f)])
       (if k
           (ff)
           (with-continuation-mark 'errortrace-k 'f
             (ff)))))))

(define (handler ex)
  (continuation-mark-set->list (exn-continuation-marks ex) 'errortrace-k))

(define (fact m)
  (wrap (let loop ([n m])
          (wrap (cond
                  [(wrap (zero? n)) (wrap (/ 1 0))]
                  [else (wrap (* (wrap n) (wrap (loop (wrap (sub1 n))))))])))))

(with-handlers ([exn:fail? handler])
  (wrap (fact 5)))

which produces:

'((loop (wrap (sub1 n)))
  (loop (wrap (sub1 n)))
  (loop (wrap (sub1 n)))
  (loop (wrap (sub1 n)))
  (loop (wrap (sub1 n)))
  (fact 5))

This result is more aligned with the traditional stacktrace, and gives
useful information that I can use to trace to the error location.

It is also safe-for-space:

(define (fact m)
  (wrap (let loop ([n m] [acc 1])
          (wrap (cond
                  [(wrap (zero? n)) (wrap (/ 1 0))]
                  [else (wrap (loop (wrap (sub1 n)) (wrap (* n acc))))])))))

(with-handlers ([exn:fail? handler])
  (wrap (fact 5)))

produces:

'((fact 5))

Now, the question: why is the current errortrace implemented in that way?
Am I missing any downside of this new strategy? Would switching and/or
integrating with the new strategy be better?

Thanks,
Sorawee (Oak)

-- 
You received this message because you are subscribed to the Google Groups 
"Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to racket-users+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/racket-users/CADcueguwj1rK0oBAj3m2eiv_h94GGSOQP67g5Rxst%2BC4qWjwHg%40mail.gmail.com.

Reply via email to