Try Firefox!

SI413 Scheme Lab 8: Objects and some misc stuff we need for them

A few nice features: begin and functions with a variable number of arguments

Closures
Consider the following example:
> (define tot (lambda (x) (let ((b 0)) (set! b (+ b x)) b)))
> (tot 3)
3
> (tot 5)
5
> (tot 0)
0
	
The function tot has no memory. The second call, (tot 5), returns 5 rather than 8 because the variable b is born when tot is called and dies when tot returns. It doesn't live on from function call to function call to retain any value - it's a regular old local variable. Now let's change our definition slightly and see what happens.
> (define tot (let ((b 0)) (lambda (x) (set! b (+ b x)) b)))
> (tot 3)
3
> (tot 5)
8
> (tot 0)
8
It's not entirely clear what we should expect to happen in the preceeding function, where the lambda-expression appears inside of a let. The designers of scheme chose to interpret it so that b lives as long as the function itself, not merely as long as one function call. Therefore we get a function with memory, as the example illustrates. This kind of a thing - a function wrapped in data - is called a closure.

Object Oriented Programming in Scheme
The C++ view of object-oriented programming is "I've got this data, wouldn't it be nice to package it together with functions?" The Scheme view is "I've got functions, wouldn't it be nice to package them together with data?" Either way, you get to the same place. Closures, of course, is how you package data together with a function. Let's see how that might give you an "object". In particular, let's make an object that models a bank account, where you can make deposits and withdrawls.

First of all notice that a closure is a single function, so that the C++/Java model of an object with lots of member functions is not quite what we'll have. Instead, we will view ourselves as interacting with objects by sending messages. Thus, the function that is our object is a message-processing function. So instead of calling a member function withdraw, we will send the 'withdraw message. And instead of calling a member function deposit, we will send the 'deposit message. So the function that is our closure will take two arguments: the message, which will be 'deposit or 'withdraw and the data associated with the message, which will simply be the amount.

(define bankAcct (let ((bal 0)) 
                   (lambda (message data)
                     (cond ((equal? message 'deposit) (set! bal (+ bal data)) bal)
                           ((equal? message 'withdraw) (set! bal (- bal data)) bal)
                           ((#t (display "Unknown message!") 'error))))))
This creates a new closure named bankAcct, which has as its only persistant data bal. Since bankAcct is a closure, it is a function. When you call it, it expects two arguments - message and data. Here's how you might use this object:
> (bankAcct 'deposit 10.50)
10.5
> (bankAcct 'withdraw 6.75)
3.75

Problems
  1. Define a function min-sin that takes one or more arguments and returns the argument whose sin is smallest. If the user calls the function with no arguments, the interpreter should give an error! In DrScheme this means you'll see the

  2. Extend the bank account example above with the following new messages:
    1. 'balance' -- prints the current balance (no argument)
    2. 'accrue' -- add 1 year of simple interest to the balance (no argument). At first assume an interest rate of 3%.
    3. 'setrate ' -- change the interest rate to the given argument. .07 would indicate 7%.
    4. 'withdraw' -- modify this message so that if there is not enough money in the account, a warning is printed and no money is withdrawn.
    Sample code to test with:
    (bankAcct 'deposit 1100)
    (bankAcct 'withdraw 100)
    (bankAcct 'balance)
    (bankAcct 'accrue)
    (bankAcct 'withdraw 30)
    (display  "About to set new rate\n")
    (bankAcct 'setrate .10)
    (bankAcct 'accrue)
    (bankAcct 'accrue)
    (display  "About to withdraw 2000\n")
    (bankAcct 'withdraw 2000)
    (display  "About to withdraw 1000\n")
    (bankAcct 'withdraw 1000)
    
    which should yield these results:
    1100
    1000
    1000
    1030.0
    1000.0
    About to set new rate
    1000.0
    1100.0
    1210.0
    About to withdraw 2000
    No way!  Your balance is only: 1210.0
    1210.0
    About to withdraw 1000
    210.0
    
  3. (OPTIONAL) Write a function group whose first argument is an integer k that takes its remaining arguments and "groups" them such that each consecutive k arguments becomes a group. The function returns a list of groups ... where each group is itself a list. Note: This is a bit tricky -- think about what useful helper functions would be!
    > (group 2 'a 1 'b 2 'c 3)
    ((a 1) (b 2) (c 3))
    > (group 3 'a 1 'b 2 'c 3)
    ((a 1 b) (2 c 3))
    > (group 4 'a 1 'b 2 'c 3)
    ((a 1 b 2) (c 3))
    > (group 5)
    ()
    > 
    


Christopher W Brown
Last modified: Wed Aug 27 09:02:14 EDT 2008