UNB/ CS/ David Bremner/ teaching/ cs2613/ labs/ Lab 7

Background

Combinators

Time
25 minutes
Activity
Small groups / individuals

We already learned about higher order functions, functions which take other functions as arguments. Certain higher order functions return other functions; these are sometimes called combinators, especially when the "combine" two or more functions.

    #lang racket
    (require plot)
    (require "../L06/calculus.rkt")
    (lambda (x) (- (deriv sin x) (cos x)))
    (define (fsub f g)
      (lambda (x) (                ))

    (module+ test
      (require rackunit)
      (define (sin2 x) (integrate cos x))
      (define (cos2 x) (deriv sin x))
      (define epsilon 0.001)
      (define test-points (build-list 20 (lambda (x) (* 2 pi (random)))))
      (for ([x test-points])
        (check-= ((fsub cos cos2) x) 0 epsilon)
        (check-= ((fsub sin sin2) x) 0 epsilon)))
    (plot (function (fsub (lambda (x) (deriv sin x)) cos) -2pi 2pi))
    (define (fderiv f)
      (lambda (x) (deriv f x)))

    (plot (function (fsub (fderiv sin) cos) -2pi 2pi))
    (define (binop->fbinop op)
      (lambda (f g)
        (lambda (x)                 )))

    (define fsub2 (binop->fbinop -))

    (plot (function (fsub2 (fderiv sin) cos) -2pi 2pi)))

    (define fadd (binop->fbinop +))
    (plot (function (fadd  sin cos) -2pi 2pi))

Macros

In the first part of the lab we discussed a method of building programs at runtime using combinators. In the rest of the lab, 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

Time
10 minutes
Activity
Demo, Group-Discussion.
    #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

Time
15 minutes
Activity
Individual work, small groups
    (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)))

Pattern Matching

One very useful feature of racket is pattern matching. This is used in syntax-case to define macros, but also in more familiar function definitions using match.

Roughly speaking, you can think about match as fancy cond, where instead of specifying a test for each case, you specify a pattern to match against the data.

map using match

Time
10 min
Activity
Demo
    (define (my-map f lst)
      (match lst
        ['() '()]
        [(cons head tail) (cons (f head)
                                (my-map f tail))]))

    (module+ test
      (require rackunit)
      (check-equal? (my-map sub1 '(1 2 3)) '(0 1 2)))
    (define (my-map2 f lst)
      (match lst
        ['() '()]
        [(list head tail ...) (cons (f head)
                                    (my-map2 f tail))]))

Let and Let*

Time
25 min
Activity
Individual work / small-groups

We have discussed the difference between let and let* a bit in the labs. It turns out that let* can be implimented in terms of let using a macro. In this section we'll do the

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.

Time
10 min
Activity
Demo
    (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))

Before you Leave

Commit and push the .rkt files you wrote today.