[plt-scheme] to define, or to let

From: Robby Findler (robby at cs.uchicago.edu)
Date: Sun Mar 21 18:05:47 EST 2004

Out of curiosity, I wrote a little script (below) to test how many
letrecs in our CVS repository have "unprotected" references to
letrec-bound variables. The script takes advantage of the fact that
fully expanded mzscheme code still contains letrec (technically,
letrec-values).

Surprisingly, only 1 of the 1221 letrecs has such an unprotected
reference; here it is (from plt/collects/algol60/runtime.ss):

   (define undefined (letrec ([x x]) x))

This does count internal definitions (since they expand to letrec) but
not units (they expand directly to lets+set!s) or modules (their
top-levels stays as definitions). If units and modules were counted,
there would probably be many more.

Robby

;; below, "bad" letrecs are those that don't pass the syntactic
;; test and "good" ones are ones that do pass the syntactic test.

;; letrecs : number
;; total count of letrecs
(define letrecs 0)

;; failed-test : number
;; total count of "bad" letrecs
(define failed-test 0)

;; process-directory : string[directory] -> void
;; recursively traverses `dir' and all its subdirectories,
;; looking for files to send to process-file.
(define (process-directory dir)
  (for-each
   (lambda (file)
     (let ([c (build-path dir file)])
       (cond
         [(file-exists? c) (process-file dir c)]
         [(directory-exists? c) (process-directory c)]
         [else (void)])))
   (directory-list dir)))

;; process-file : string[directory] string[filename] -> void
;; prints the name of the file before processing it
;; prints the current total letrec count and
;; the total count of "bad" letrecs after processing the file
(define (process-file dir filename)
  (printf "testing ~s\n" filename)
  (find-letrec
   (with-handlers ([not-break-exn? (lambda (x) (void))])
     (parameterize ([current-load-relative-directory dir]
                    [current-directory dir])
       (expand 
        (call-with-input-file filename
          (lambda (port)
            (read-syntax filename port)))))))
  (printf "letrecs ~s failed test ~s\n" letrecs failed-test))

;; find-letrec : stx -> void
;; finds all of the letrecs in some syntax
(define (find-letrec stx)
  (let loop ([stx stx])
    (syntax-case stx (letrec)
      [(letrec-values (((x ...) rhs) ...) body ...)
       (set! letrecs (+ letrecs 1))
       (unless (found-one (syntax (((x ...) rhs) ...)))
         (set! failed-test (+ failed-test 1)))]
      [(x ...)
       (for-each loop (syntax->list (syntax (x ...))))]
      [else (void)])))

;; found-one : stx[letrec, binders section] -> boolean
;; called to inspect a letrec
;; returns #t to indicate the letrec is "good" and #f for "bad"
(define (found-one stxs)
  (syntax-case stxs ()
    [([(x ...) rhs] ...)
     (let ([vars (apply append (map syntax->list (syntax->list (syntax ((x ...) ...)))))])
       (let/ec fail
         (for-each (lambda (rhs) (explore-rhs rhs vars (lambda () (fail #f))))
                   (syntax->list (syntax (rhs ...))))
         #t))]))

;; explore-rhs : stx (listof identifer) (-> void) -> void
;; bad-var escapes
;; looks for a variable in rhs that is module-identifier=? to one of vars
;; if found, calls `bad-var'. Otherwise, just returns
(define (explore-rhs rhs vars bad-var)
  (let loop ([stx rhs])
    (syntax-case rhs (lambda case-lambda)
      [(lambda . rest) (void)]
      [(case-lambda . rest) (void)]
      [(quote . rest) (void)]
      [(quote-syntax . rest) (void)]
      [(x ...) (for-each loop (syntax->list (syntax (x ...))))]
      [x 
       (identifier? (syntax x))
       (when (ormap (lambda (var) (module-identifier=? (syntax x) var))
                    vars)
         (bad-var))]
      [x (void)])))

;; get the ball rolling
(process-directory (current-directory))


Posted on the users mailing list.