On Scheme

Thoughts on Scheme and teaching Scheme

Archive for March 10th, 2006

destructuring-bind

Posted by Peter on March 10, 2006

I really only implemented destructuring-bind because of the incomplete function calls macro. I expect that destructuring-bind will be useful in other macros as well. Destructuring-bind is implemented in common lisp, my implementation tries to mimic it to some extend, but I currently do not implement the entire functionality of the common lisp version. Basically you call destructuring-bind with a form, an expression, and a body. Destructuring-bind inspects the form of the expression, and defines each symbol found within the value at the same “position” within the result of the expression. For example in destructuring-bind (a b c) (list 1 2 3) … a will be 1, b will be 2, ect. However this works for more than one layer of nesting depth in the list, so in the call destructuring-bind (a (b c) d (e)) '(1 (2 3) (4 5) (6)) … a will be equal to 1, b to 2, c to 3, d to the list ‘(4 5), and e to 6. These assignments are the same as those created by a let statement for the body of the destructuring-bind. A feature imported from common list is the &rest argument. The idea is that after encountering &rest in a list within the form any remaining elements in that list will be put into a new list and assigned to the symbol following the &rest. For example in destructuring-bind (a (b c &rest d) &rest f) '(1 (2 3 4 5 6) 7 8 9) … a will be equal to 1, b to 2, c to 3, d to the list ‘(4 5 6), and f to the list ‘(7 8 9). I haven’t yet implemented the &optional, nor do multiple variable names after a &rest throw an error yet, as they should.
My first implementation of this macro was thus:

(define-syntax destructuring-bind
    (lambda (x)
      (letrec (
               (gen-let-bindings
                (lambda (expr form)
                  (cond ((null? form) '())
                        ((pair? form)
                         (if (eq? (car form) '&rest)
                             (list (list (car (cdr form)) expr))
                             (append
                              (gen-let-bindings (list 'car expr) (car form))
                              (gen-let-bindings (list 'cdr expr) (cdr form)))))
                        (else (list (list form  expr))))))
               (finaltransformer
                (lambda (form expr body)
                  (let ((tsym (gensym)))
                    `(let ((,tsym ,expr)) (let ,(gen-let-bindings tsym form) ,@body)))))
               )
        (let ((syn (cdr (syntax-object->datum x))))
          (datum->syntax-object (syntax k) (finaltransformer
                                            (car syn)
                                            (car (cdr syn))
                                            (cdr (cdr syn))))))))

This worked fine for most use within a function. However to get it to work within another macro, like I wanted you have to jump through some more complications. To make a long story short you must define it within its own module and then use the require-for-syntax directive to import it. (along with the require directive to use it in normal functions. Thus (if we have it in the same file as the functions and macros that are using it) it should be defined thus:

(module destructuring-bind mzscheme    
  (require-for-template mzscheme)
  
  (provide destructuring-bind)

  (define-syntax destructuring-bind
    (lambda (x)
      (letrec (
               (gen-let-bindings
                (lambda (expr form)
                  (cond ((null? form) '())
                        ((pair? form)
                         (if (eq? (car form) '&rest)
                             (list (list (car (cdr form)) expr))
                             (append
                              (gen-let-bindings (list 'car expr) (car form))
                              (gen-let-bindings (list 'cdr expr) (cdr form)))))
                        (else (list (list form  expr))))))
               (finaltransformer
                (lambda (form expr body)
                  (let ((tsym (gensym)))
                    `(let ((,tsym ,expr)) (let ,(gen-let-bindings tsym form) ,@body)))))
               )
        (let ((syn (cdr (syntax-object->datum x))))
          (datum->syntax-object x (finaltransformer
                                            (car syn)
                                            (car (cdr syn))
                                            (cdr (cdr syn))))))))
)

(require-for-syntax destructuring-bind)
(require destructuring-bind)

If you look closely you will notice that there is one other change, instead of datum->syntax-object (syntax k) … I now have datum->syntax-object x …. All I know is that without this change when the macro expands it won’t bind the symbols in the proper context, but I don’t really understand what (syntax k) is doing (which is what they use in all the examples) and it is possible that replacing it with x has nasty side effects I just haven’t seen yet. So if anyone knows what the difference is, and can give an explanation, I would be happy to hear from them.

You may have noticed that I seem to be adopting many features from common lisp into scheme. Why don’t I just use common lisp then? Good question, and I promise to post a rationale for why I feel that scheme is superior to common lisp soon.

One final note, some pretty good documentation for scheme macros (much better than the PLT language manual I commonly refer to is available here.

Posted in Exploring Scheme | 1 Comment »

 
Follow

Get every new post delivered to your Inbox.