UNB/ CS/ David Bremner/ teaching/ cs3613/ tutorials/ CS3613 Tutorial 6

Getting started

Open a new file in DrRacket and add the following definitions to the beginning. This makes available some definitions from racket in plai-typed.

#lang plai-typed

(module tutorial6 typed/racket/base
  (require racket/math)
  (require (prefix-in plot: plot))

  (provide plot sin cos pi)

  (define (plot [fn : (Real -> Real) ] [from : Real] [to : Real])
    (plot:plot
     (plot:function fn from to)))
)


(require (typed-in 'tutorial6
                   [plot : ((number -> number) number number -> void)]
                   [pi : number]
                   [sin : (number -> number)]
                   [cos : (number -> number)]))

For the rest of the tutorial, add various definitions discussed to your file. Make a note of the types of the functions discussed.

Derivatives and integrals

We can (crudely) approximate a derivative with the following higher order function.

(define dx 0.001)

;; compute the derivative of `f` at the given point `x`
(define (deriv f x)
  (/ (- (f (+ x dx)) (f x)) dx))

Similarly, we can approximate the integral of a function at a point (more precisely from 0 to the point):

;; compute an integral of `f' at the given point `x'
(define (integrate f x)
  (local
      [(define (loop y acc)
         (if (> y x)
             (* acc dx)
             (loop (+ y dx) (+ acc (f y)))))]
    (loop 0 0)))

And say that we want to try out various functions given some `plot' function that draws graphs of numeric functions, for example:

    (define -2pi (* -2 pi))
    (define 2pi (* 2 pi))
    (plot sin -2pi 2pi)

The problem is that plot expects a single (number -> number) function -- if we want to try it with a derivative, we can do this:

;; the derivative of sin

(define sin-deriv (lambda (x) (deriv sin x)))
(plot sin-deriv -2pi 2pi)

But this will get very tedious very fast -- it is much simpler to use an anonymous function:

(plot (lambda (x) (deriv sin x)) -2pi 2pi)

we can even verify that our derivative is correct by comparing a known function to its derivative. Fill in the blank in the following with the derivative of sin

(plot (lambda (x) (- (deriv sin x)         )) -2pi 2pi)

But it's still not completely natural to do these things -- you need to explicitly combine functions, which is not too convenient. What can you observe about the numerical error here?

Combinators

Instead of explicitely combining , we can write H.O. functions that will work with functional inputs and outputs. Such functions are called 'combinators. For example, we can write a function to subtract functions:

 (define (fsub f g)
    (lambda (x) (- (f x) (g x))))

Notice that the inferred type for fsub is a bit too general. Add type annotations to fix this.

We can similarly mamke a combinator to compute (indefinite) integrals.

 (define (fderiv f)
    (lambda (x) (deriv f x)))

Now we can try the same error calculation in a much easier way:

 (plot (fsub (fderiv sin) cos) -2pi 2pi)

More than that -- our fderiv could be created from deriv automatically:

  ;; convert a double-argument function to a curried one
  (define (currify f)
    (lambda (x) (lambda (y) (f x y))))

  ;; compute the derivative function of `f'
  (define fderiv2 (currify deriv))

Same principle with fsub: we can write a function that converts a binary arithmetical function into a function that operates on unary numeric function. But to make things more readable we can define new types for unary and binary numeric functions:

;; turns an arithmetic binary operator to a function operator
(define (binop->fbinop op)
  (lambda (f g)
    (lambda (x) (op (f x) (g x)))))

Use binop->fbinop to define a new version of fsub (e.g. calling it fsub2).

As a final exercise, define a combinator called "fintegrate" to make the following plot call work:

;; want to verify that `integrate' is the opposite of `deriv':
;;   take a function, subtract it from its derivative's integral

(plot (fsub sin (fintegrate (fderiv2 sin))) 0 2pi)

This is one line if you use currify.