We started with an inefficient version of the fib function for computing Fibonacci numbers:
(define (fib n) (if (<= n 1) 1 (+ (fib (- n 1)) (fib (- n 2)))))The complexity of this function is exponential. It is possible to rewrite the function using an iterative approach that runs much faster, but we can use memoization instead. We set up a global variable called answers with the first two values:
(define answers '((0 . 1) (1 . 1)))Then in writing the function, we first did a lookup against our list of answers. If we've already computed that Fibonacci number, then we just return its value. Otherwise we compute an answer and store it in our list of answers so that we never compute it again:
(define (fib n) (let ((match (assoc n answers))) (if match (cdr match) (let ((new-answer (+ (fib (- n 1)) (fib (- n 2))))) (begin (set! answers (cons (cons n new-answer) answers)) new-answer)))))We saw that as we asked for higher values of fib, our global variable ended up with more memoized results:
> answers ((0 . 1) (1 . 1)) > (fib 5) 8 > answers ((5 . 8) (4 . 5) (3 . 3) (2 . 2) (0 . 1) (1 . 1)) > (fib 20) 10946 > answers ((20 . 10946) (19 . 6765) (18 . 4181) (17 . 2584) (16 . 1597) (15 . 987) (14 . 610) (13 . 377) (12 . 233) (11 . 144) (10 . 89) (9 . 55) (8 . 34) (7 . 21) (6 . 13) (5 . 8) (4 . 5) (3 . 3) (2 . 2) (0 . 1) (1 . 1))We then looked at how to localize answers. Instead of using a global variable, we can use a let inside the function and define a helper function that has access to it:
(define (fib n) (let ((answers '((0 . 1) (1 . 1)))) (define (helper n) (let ((match (assoc n answers))) (if match (cdr match) (let ((new-answer (+ (helper (- n 1)) (helper (- n 2))))) (set! answers (cons (cons n new-answer) answers)) new-answer)))) (helper n)))This is a pretty good answer in that every time you call fib, it sets up a variable called answers that memoizes the results. But we can do even better. Why reconstruct answers every time you call fib? If we instead want to construct the answers just once, then we don't want fib to be defined as a normal function as we have done above. Instead, we want to set it up just once and make it a function that has access to answers. But we've already done that. Our helper function is the function we want fib to be, so all we have to do is assign fib to be the helper:
(define fib (let ((answers '((0 . 1) (1 . 1)))) (define (helper n) (let ((match (assoc n answers))) (if match (cdr match) (let ((new-answer (+ (helper (- n 1)) (helper (- n 2))))) (set! answers (cons (cons n new-answer) answers)) new-answer)))) helper))This assignment happens just once, so that we construct the answers just once. That means that we'll never end up computing the same value of fib more than once.
I then spent some time discussing how to simulate objects in Scheme. In a language like Java, we obtain an object by calling its constructor. In Scheme we can introduce a function that constructs the equivalent of an object:
(define (make-foo) ; simulates a constructor ...When you construct an object, you allocate space for each of its fields. We can simulate thi with a let that defines some variables in an inner scope:
(define (make-foo) ; simulates a constructor (let ((f1 e1) (f2 e2) (f3 e3) ... (fn en)) ; simulates fields ...In addition to fields, an object has various methods that can act on those fields. We can simulate that by having a series of functions definined in the inner scope:
(define (make-foo) ; simulates a constructor (let ((f1 e1) (f2 e2) (f3 e3) ... (fn en)) ; simulates fields (define (m1 params) expr) ; simulates methods (define (m2 params) expr) (define (m3 params) expr) ...But how do we gain access to one of several different methods? We do so by introducing a method that takes a method name as an argument and that calls one of the methods in the inner scope:
(define (make-foo) ; simulates a constructor (let ((f1 e1) (f2 e2) (f3 e3) ... (fn en)) ; simulates fields (define (m1 params) expr) ; simulates methods (define (m2 params) expr) (define (m3 params) expr) (define (dispatch method-name) (cond ((eq? method-name 'm1) m1) ((eq? method-name 'm2) m2) ... (else (error "no such method")))) dispatch))Notice that the dispatch function is what you are returned by the constructor. We looked at an example bankaccount.scm:
;;; bank account example - simulating object-oriented programming in Scheme ;;; ;;; (program adapted from one in 'Structure and Interpretation of ;;; Computer Programs') (define (make-account) (let ((my-balance 0)) ;; return the current balance (define (balance) my-balance) ;; make a withdrawal (define (withdraw amount) (if (>= my-balance amount) (begin (set! my-balance (- my-balance amount)) my-balance) "Insufficient funds")) ;; make a deposit (define (deposit amount) (set! my-balance (+ my-balance amount)) my-balance) ;; the dispatching function -- decide what to do with the request (define (dispatch m) (cond ((eq? m 'balance) balance) ((eq? m 'withdraw) withdraw) ((eq? m 'deposit) deposit) (else (error "Unknown request -- MAKE-ACCOUNT" m)))) dispatch)) ;;; some commands to try: ;;; (define acct1 (make-account)) ;;; (define acct2 (make-account)) ;;; ((acct1 'balance)) ;;; ((acct1 'deposit) 100) ;;; ((acct1 'withdraw) 30) ;;; ((acct1 'withdraw) 200) ;;; ;;; ((acct2 'balance)) ; this is a different account from acct1!