UNB/ CS/ David Bremner/ teaching/ cs4613/ tutorials/ Macros

In this tutorial, we'll discuss the construction of programs at compile time using what are called macros.

Some Racket forms like and and or look like function calls, but we know they are not because they support short-circuit evaluation. We can define forms that behave differently than function calls (or that look like function calls, but actually do other things as well, like the rackunit check- forms). These forms are sometimes called macros.

Redefining and

Prerequisites
tests, quick
    #lang racket
    (define-syntax-rule (And a b)
      (if b a #f))

    (module+ test
      (require rackunit)
      (define (die)
        (error 'die "don't run this"))

      (check-equal? (And (die) #f) #f)
      (check-exn exn:fail? (lambda () (and (die) #f))))
    (module+ test
      (define-syntax-rule (check-fail expr)
        (check-exn exn:fail? (lambda () expr)))
      (check-fail (and (die) #f))
      (check-fail (And #f (die))))

Redefining or

    (module+ test
      (check-equal? (Or #t #t) #t)
      (check-equal? (Or #f #t) #t)
      (check-equal? (Or #t #f) #t)
      (check-equal? (Or (die) #t) #t)
      (check-fail (or (die) #t)))

Let and Let*

Prerequisites
recursion, modules

In homework 3 we implimented an evalator for with* using with. It turns out that let* can be implimented in a similar way in terms of let using a macro.

Let's start by reviewing let*:

    (module+ test
      (check-equal? (let* ([x 5]
                           [y (- x 3)])
                          (+ y y))
                    4)
      (check-equal? (let* ([x 5]
                           [y (- x 3)]
                           [y x])
                           (* y y))
                    25))
    (module+ test
      (check-equal? (let ([x 5])
                      (let ([y (- x 3)])
                        (+ y y)))
                    4)
      (check-equal? (let ([x 5])
                      (let ([y (- x 3)])
                        (let ([y x])
                          (* y y))))
                    25))
    (provide let-transformer)
    (define (let-transformer lst)
      (match lst
        [(list 'Let* '() body)     ]
        [(list 'Let* (cons (list       ) tail) body)
         (list 'let  (list (list id val))
               (let-transformer
                (list 'Let*          )))]))

    (module+ test
      (require rackunit)
      (check-equal? (let-transformer '(Let* ([x 5]
                                             [y (- x 3)])
                                            (+ y y)))
                    '(let ([x 5]) (let ([y (- x 3)]) (+ y y)))))

From list transformers to macros.

Prerequisites
match
    (require (for-syntax racket/match))
    (require (for-syntax "let-transformer.rkt"))

    (define-syntax (Let* stx)
      (datum->syntax #'here (let-transformer (syntax->datum stx))))
    (define-syntax (Let^ stx)
      (syntax-case stx ()
        [(Let^ () body) #'body]
        [(Let^ ((first-id first-val) (id val) ...) body)
         #'(let ([first-id first-val])
             (Let^ [(id val) ...] body))]))

    (module+ test
      (require rackunit)
      (check-equal? (Let^ ([x 5] [y (- x 3)]) (+ y y)) 4)
      (check-equal? (Let^ ([x 5] [y (- x 3)] [y x]) (* y y)) 25))