[plt-scheme] Creating new syntax for decision tables

From: Daniel Yoo (dyoo at cs.wpi.edu)
Date: Fri Feb 9 13:45:37 EST 2007


> (define-syntax table
>   (syntax-rules (:)
>     [(table (e : c1 c2 ...) ...)
>      (or (and (clause-transform c1 e) ...)
>          (table (e : c2 ...) ...))]
>     [(table (e :) ...)
>      #f]
>     [(table (e1 : c1 c2 ...) (e2 :))
>      (error "Table mismatch for rows"
>             (quote e1) 'and (quote e2))]
>     [(table (e1 :) (e2 : c1 c2 ...))
>      (error "Table mismatch for rows"
>             (quote e1) 'and (quote e2))]))

> 1. If I want to prevent the expressesions on the left side of the colons
>    from being evaluated more than once, what is the best way to do this?
>    My initial thought it to make another macro which passes those
>    expressions into a function call so that they are evaluated just
>    once.


Hi Andrew,

One way to do this is to use LET.  Unfortunately, I'm not so familiar with 
writing things with syntax-rules, but here's a version that uses 
syntax-case.  The idea is to generate a bunch of temporary names, one for 
each e, and then use the let-ed values in the table's case analysis 
instead:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(module example mzscheme
   (provide table)

   (define-syntax (table stx)
     (syntax-case stx (:)
       [(_ (e : c1 c2 ...) ...)
        (with-syntax ([(ev ...)
                       (generate-temporaries (syntax (e ...)))])
          (syntax/loc stx
            (let ([ev e] ...)
              (table-internal (ev : c1 c2 ...) ...))))]
       [(_ (e :) ...)
        (syntax/loc stx #f)]
       [(_ (e1 : c1 c2 ...) (e2 :))
        (error "Table mismatch for rows"
               (quote e1) 'and (quote e2))]
       [(_ (e1 :) (e2 : c1 c2 ...))
        (error "Table mismatch for rows"
               (quote e1) 'and (quote e2))]))


   (define-syntax table-internal
     (syntax-rules (:)
       [(_ (e : c1 c2 ...) ...)
        (or (and (clause-transform c1 e) ...)
            (table-internal (e : c2 ...) ...))]
       [(_ (e :) ...)
        #f]
       [(_ (e1 : c1 c2 ...) (e2 :))
        (error "Table mismatch for rows"
               (quote e1) 'and (quote e2))]
       [(_ (e1 :) (e2 : c1 c2 ...))
        (error "Table mismatch for rows"
               (quote e1) 'and (quote e2))]))

   (define-syntax clause-transform
     (syntax-rules (T F *)
       [(clause-transform T e) e]
       [(clause-transform F e) (not e)]
       [(clause-transform * e) #t]
       [(clause-transform other e)
        (error "Unknown flag: " (quote other))])))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

This way, the TABLE-INTERNAL macro only deals with simple values.


Unfortunately, although this avoids duplicate evaluation, it still 
evaluates all the values of the E's, even if we might not need them.

;;;;;;;;;;;;;;;;;;;;;;;;;;;
> (define-values (f g h) (values (lambda () #t)
                                  (lambda () #t)
                                  (lambda () #t)))
> (require (lib "trace.ss"))
> (trace f g h)
(f g h)
> (table ((f) : * *)
          ((g) : T T)
          ((h) : F T))
|(f)
|#t
|(g)
|#t
|(h)
|#t
#t
;;;;;;;;;;;;;;;;;;;;;;;;;;;



Another similar approach might be to do what you're thinking, building a 
function that remembers what it returns, so that we avoid evaluation 
unless absolutely necessary.

Thankfully, Dave Herman did most of the hard work in his memoize package:

     http://planet.plt-scheme.org/#memoize.plt

We can take advantage of memoize's MEMO-LAMBDA, and build a corresponding 
thunk for each E.

So the code above for the public-facing table macro can turn into:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(module example mzscheme
   (provide table)
   (require (planet "memoize.ss" ("dherman" "memoize.plt" 2 1)))

   (define-syntax (table stx)
     (syntax-case stx (:)
       [(_ (e : c1 c2 ...) ...)
        (with-syntax ([(e-thunk ...)
                       (generate-temporaries (syntax (e ...)))])
          (syntax/loc stx
            (let ([e-thunk (memo-lambda () e)] ...)
              (table-internal (e-thunk : c1 c2 ...) ...))))]
       [(_ (e :) ...)
        (syntax/loc stx #f)]
       [(_ (e1 : c1 c2 ...) (e2 :))
        (error "Table mismatch for rows"
               (quote e1) 'and (quote e2))]
       [(_ (e1 :) (e2 : c1 c2 ...))
        (error "Table mismatch for rows"
               (quote e1) 'and (quote e2))]))


   (define-syntax table-internal
     (syntax-rules (:)
       [(_ (e-thunk : c1 c2 ...) ...)
        (or (and (clause-transform c1 e-thunk) ...)
            (table-internal (e-thunk : c2 ...) ...))]
       [(_ (e-thunk :) ...)
        #f]
       [(_ (e1-thunk : c1 c2 ...) (e2-thunk :))
        (error "Table mismatch for rows"
               (quote e1-thunk) 'and (quote e2-thunk))]
       [(_ (e1-thunk :) (e2-thunk : c1 c2 ...))
        (error "Table mismatch for rows"
               (quote e1-thunk) 'and (quote e2-thunk))]))

   (define-syntax clause-transform
     (syntax-rules (T F *)
       [(clause-transform T e-thunk) (e-thunk)]
       [(clause-transform F e-thunk) (not (e-thunk))]
       [(clause-transform * e-thunk) #t]
       [(clause-transform other e-thunk)
        (error "Unknown flag: " (quote other))])))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;



And we can see that this works out:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
> (define-values (f g h) (values (lambda () #t)
                                  (lambda () #t)
                                  (lambda () #t)))
> (trace f g h)
(f g h)
> (table ((f) : * *)
          ((g) : T T)
          ((h) : F T))
|(g)
|#t
|(h)
|#t
#t
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


Hurrah, it's not evaluating the f function anymore.


Best of wishes!


Posted on the users mailing list.