[plt-dev] struct match-expander a la struct-copy

From: Jay McCarthy (jay.mccarthy at gmail.com)
Date: Tue Jun 2 22:49:20 EDT 2009

The structure patterns in scheme/match have always bugged me that they
didn't let you put the fields in any order and you had to put in _ for
the fields you didn't care about.

I've remedied this with a new match expander, struct*. I'd like to put
in the tree. Do people agree?

-- Use cases --

(define-struct super-tree (left right extra))

(define example1
  (make-super-tree 1 2 "And a pony!"))

(match example1
  [(struct* super-tree
            ([left (? number? l)]
             [right (? number? r)]))
   (printf "All numbers! ~a ~a~n" l r)])

(match example1
  [(struct* super-tree
            ([right (? number? r)]
             [left (? number? l)]))
   (printf "Look ma, any order! ~a ~a~n" l r)])

(define-struct (even-better-tree super-tree) (more-extra-good))

(define example2
  (make-even-better-tree 1 2 "And a pony!" "And a giraffe!"))

(match example2
  [(struct* super-tree
            ([left (? number? l)]
             [right (? number? r)]))
   (printf "All numbers! ~a ~a~n" l r)])

(match example2
  [(struct* even-better-tree
            ([more-extra-good (? string? m)]))
   (printf "~a~n" m)])

-- Code & Test Cases --

#lang scheme
(require (for-syntax scheme/struct-info
                     syntax/boundmap
                     scheme/list))

(define-match-expander
  struct*
  (lambda (stx)
    (syntax-case stx ()
      [(_ struct-name (field+pat ...))
       (let* ([fail (lambda ()
                      (raise-syntax-error
                       'struct* "not a structure definition"
                       stx #'struct-name))]
              [v (syntax-local-value #'struct-name fail)]
              [field-acc->pattern (make-free-identifier-mapping)])
         (unless (struct-info? v) (fail))
         ; Check each pattern and capture the field-accessor name
         (for-each (lambda (an)
                     (syntax-case an ()
                       [(field pat)
                        (unless (identifier? #'field)
                          (raise-syntax-error
                           'struct* "not an identifier for field name"
                           stx #'field))
                        (let ([field-acc
                               (datum->syntax #'field
                                              (string->symbol
                                               (format "~a-~a"
                                                       (syntax-e #'struct-name)
                                                       (syntax-e #'field)))
                                              #'field)])
                          (when (free-identifier-mapping-get
field-acc->pattern field-acc (lambda () #f))
                            (raise-syntax-error 'struct* "Field name
appears twice" stx #'field))
                          (free-identifier-mapping-put!
field-acc->pattern field-acc #'pat))]
                       [_
                        (raise-syntax-error
                         'struct* "expected a field pattern of the
form (<field-id> <pat>)"
                         stx an)]))
                   (syntax->list #'(field+pat ...)))
         (let* (; Get the structure info
                [acc (fourth (extract-struct-info v))]
                ;; the accessors come in reverse order
                [acc (reverse acc)]
                ;; remove the first element, if it's #f
                [acc (cond [(empty? acc) acc]
                           [(not (first acc)) (rest acc)]
                           [else acc])]
                ; Order the patterns in the order of the accessors
                [pats-in-order
                 (for/list ([field-acc (in-list acc)])
                   (begin0
                     (free-identifier-mapping-get
                      field-acc->pattern field-acc
                      (lambda () (syntax/loc stx _)))
                     ; Use up pattern
                     (free-identifier-mapping-put!
                      field-acc->pattern field-acc #f)))])
           ; Check that all patterns were used
           (free-identifier-mapping-for-each
            field-acc->pattern
            (lambda (field-acc pat)
              (when pat
                (raise-syntax-error 'struct* "field name not
associated with given structure type"
                                    stx field-acc))))
           (quasisyntax/loc stx
             (struct struct-name #,pats-in-order))))])))

; Comment out to test syntax errors

; Bad struct info id
#;(match example1
    [(struct* some-tree
              ([left (? number? l)]
               [right (? number? r)]))
     (printf "Just the facts: ~a ~a~n"
             l r)])

; Bad struct info
(define-for-syntax uncool-tree #f)
#;(match example1
    [(struct* uncool-tree
              ([left (? number? l)]
               [right (? number? r)]))
     (printf "Just the facts: ~a ~a~n"
             l r)])

; Bad syntax form
#;(match example1
    [(struct* super-tree
              ([foo]
               [right (? number? r)]))
     (printf "Just the facts: ~a ~a~n"
             l r)])

; Not an id for field
#;(match example1
    [(struct* super-tree
              ([(+ 1 1) (? number? l)]
               [right (? number? r)]))
     (printf "Just the facts: ~a ~a~n"
             l r)])

; Field appears twice
#;(match example1
    [(struct* super-tree
              ([right _]
               [right (? number? r)]))
     (printf "Just the facts: ~a~n"
             r)])

; Not a field id
#;(match example1
  [(struct* super-tree
            ([feet (? number?)]
             [right (? number? r)]))
   (printf "Just the facts: ~a~n"
           r)])

; Super structs don't work
#;(match example2
  [(struct* even-better-tree
            ([left (? number? l)]
             [right (? number? r)]
             [more-extra-good (? string? s)]))
   (printf "All numbers (+ 1 string)! ~a ~a ~a~n" l r s)])

; Super structs don't work
#;(match example2
  [(struct* even-better-tree
            ([left (? number? l)]
             [extra (? string? e)]
             [right (? number? r)]
             [more-extra-good (? string? m)]))
   (printf "All numbers (+ 2 strings)! ~a ~a ~a ~a~n" l e r m)])

-- 
Jay McCarthy <jay at cs.byu.edu>
Assistant Professor / Brigham Young University
http://teammccarthy.org/jay

"The glory of God is Intelligence" - D&C 93


Posted on the dev mailing list.