Destructuring-Bind
April 5, 2019
We call our function bind
; it’s not quite the same as the destructuring-bind of Common Lisp, and bind
is easier to type:
(define-syntax bind (lambda (stx) ; (bind pattern values body ...) ; Pattern is a possibly-nested list of symbols, values is a list of items of ; the same "shape" as pattern, and body is a non-empty sequence of expressions. ; The bind macro executes the body expressions in an environment with each ; value let-bound to the corresponding symbol. Writing "& x" at the end of the ; pattern list causes the remaining values to be bound to x in a list. Symbol ; "_" in the pattern matches the corresponding value but causes no binding. ; Extra values are silently ignored, missing values cause an error. Examples: ; (bind (a b c) '(1 2 3) (list c b a)) => (3 2 1) ; (bind (a b c) '(1 2 3 4) (list c b a)) => (3 2 1) ; (bind (a b c) '(1 2) (list c b a)) => causes an error ; (bind (a b (c d)) '(1 2 (3 4)) (list d c b a)) => (4 3 2 1) ; (bind (a b & c) '(1 2 3 4) (list c b a)) => ((3 4) 2 1) ; (bind (a _ c) '(1 2 3) (list c a)) => (3 1) ; (bind (a (b & c)) '(1 (2 3 4)) (list c b a)) => ((3 4) 2 1) ; (let ((x 3)) (bind (a b c) '(1 (+ 1 1) x) (list c b a))) => (x (+ 1 1) 1) ; (let ((x 3)) (bind (a b c) `(1 ,(+ 1 1) ,x) (list c b a))) => (3 2 1) ; As an example, the macro-expression (bind (a b c) '(1 2 3) (list c b a)) ; expands to (let ((a 1)) (let ((b 2)) (let ((c 3)) (list c b a)))). Similar to ; Common Lisp destruturing-bind or many "match" macro libraries in Scheme. (define (underscore? x) (and (identifier? x) (free-identifier=? x (syntax _)))) (syntax-case stx (&) ((bind () vals body ...) ; end of pattern, ignore extra values (syntax (begin body ...))) ((bind (& pat) vals body ...) ; rest pattern, collect remaining values (syntax (let ((pat vals)) body ...))) ((bind (underscore) vals body ...) ; non-binding pattern at end (underscore? (syntax underscore)) (syntax (begin body ...))) ((bind (underscore pat ...) vals body ...) ; non-binding pattern not at end (underscore? (syntax underscore)) (syntax (bind (pat ...) (cdr vals) body ...))) ((bind ((nest ...) pat ...) vals body ...) ; nested pattern (syntax (bind (pat ...) (cdr vals) (bind (nest ...) (car vals) body ...)))) ((bind (pat) vals body ...) ; last binding in pattern (syntax (let ((pat (car vals))) body ...))) ((bind (pat1 pat2 ...) vals body ...) ; more bindings in pattern (syntax (let ((pat1 (car vals))) (bind (pat2 ...) (cdr vals) body ...)))))))
We have to use syntax-case
, rather than syntax-rules
, because there is no way to escape the underscore in syntax-rules
. We illustrate the bind
function by reimplementing Okasaki’s physicist’s queues from the prior exercise:
(define queue list)
(define empty (queue '() 0 (delay '()) 0 '())) (define (empty? q) (bind (_ lenf _ _ _) q (zero? lenf)))
(define (checkw q) (bind (w lenf f lenr r) q (if (null? w) (queue (force f) lenf f lenr r) q))) (define (check q) (bind (w lenf f lenr r) q (if (< lenr lenf) (checkw q) (let ((fprime (force f))) (checkw (queue fprime (+ lenf lenr) (delay (append fprime (reverse r))) 0 '()))))))
(define (snoc q x) (bind (w lenf f lenr r) q (check (queue w lenf f (+ lenr 1) (cons x r)))))
(define (head q) (bind (w _ _ _ _) q (if (null? w) (error 'head "empty queue") (car w)))) (define (tail q) (bind (w lenf f lenr r) q (if (null? w) (error 'tail "empty queue") (check (queue (cdr w) (- lenf 1) (delay (cdr (force f))) lenr r)))))
Visually, that is extremely similar to Okasaki’s code. Here is an example:
> (define q empty) > (set! q (snoc q 1)) > (set! q (snoc q 2)) > (set! q (snoc q 3)) > (head q) 1 > (set! q (tail q)) > (head q) 2 > (set! q (tail q)) > (head q) 3 > (set! q (tail q)) > (empty? q) #t
This version is more regular than the previous version (every function begins by destructuring the elements of a queue), but that regularity comes at a cost: the snoc
and tail
functions have to cons up a queue
, only to have it destructured in check
, and likewise for the call from check
to checkw
. The let-values
code of the previous exercise is more efficient, so we have traded that efficiency for readability. This program is small enough that the readability probably doesn’t matter, but in a larger program it might matter a lot.
You can run the program at https://ideone.com/66284X.