beginand functions with a variable number of arguments
beginfunction lets you do that. It simply evaluates all its arguments (and there can be as many as you like) in order, and returns the value of the last.
> (define (btest x) (if (>= x 0) (begin (display x) (display " is not negative!") #f) (begin (display x) (display " is negative!") #t))) > (btest 3) class 3 is not negative! #f
> (+ 1 2) 3 > (+ 1 2 3) 6 > (+ 3) 3 > (+) 0How can you make that happen in Scheme? Well, if in a lambda-expression you put a variable name in place of the parameter list, your function gets as its argument that variable only, and it will be a list of all the actual arguments in the function call.
> (define ave (lambda vals (/ (apply + vals) (length vals)))) > (ave 95 80 86) 87 > (ave 13) 13 > (ave 85 62 77 84 78 92 90 58) 78 1/4
aveshould be defined this way, since calling it with no arguments would give a divide-by-zero error. We can get a variable number of arguments another way that lets us specify that at least some arguments must be present. In the parameter list for the lambda-expression (or function defined in our usual way, without lambdas), specify the variables that must be there in the usual way, then put a . followed by one more variable name. The name following the period will be a list of all the optional arguments, while those variables before the . are mandatory.
> (define ave (lambda (x . vals) (/ (+ x (apply + vals)) (+ 1 (length vals))))) > (ave 1 5 7) 4 1/3 > (ave 1 5) 3 > (ave 1) 1 > (ave) procedure ave: expects at least 1 argument, given 0 >Optional arguments like this gives us a nice way to take care of the extra aruments we introduce with tail recursion. For example, the following function is a tail-recursive
powerfunction. Of course, it's got an extra "if" test in each recursive call ...
(define (power x k . L) (if (null? L) (power x k 1) (if (= k 0) (car L) (power x (- k 1) (* x (car L))))))
defines inside a lambda, a let or a begin: If you really just can't hack
letexpressions, here's a hint: you can use defines at the beginning of a "body", i.e. at the beginning of the sequence of expressions following the variable list in
let's, or immediately following the "prototype" in function defined with
define. These definitions are, in fact, exactly equivalent to using letrec's. So the following two tail-recursive defintions of power are the same:
(define (power1 x k) (letrec ((power-tr (lambda (z j c) (if (= j 0) c (power-tr z (- j 1) (* z c)))))) (power-tr x k 1))) (define (power2 x k) (define (power-tr z j c) (if (= j 0) c (power-tr z (- j 1) (* z c)))) (power-tr x k 1))
> (define tot (lambda (x) (let ((b 0)) (set! b (+ b x)) b))) > (tot 3) 3 > (tot 5) 5 > (tot 0) 0The function
tothas no memory. The second call,
(tot 5), returns 5 rather than 8 because the variable
bis born when
totis called and dies when
totreturns. 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) 8It'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
blives 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.
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
'withdraw message. And instead of
calling a member function
deposit, we will
'deposit message. So the function
that is our closure will take two arguments: the message,
which will be
'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
bankAcctis a closure, it is a function. When you call it, it expects two arguments -
data. Here's how you might use this object:
> (bankAcct 'deposit 10.50) 10.5 > (bankAcct 'withdraw 6.75) 3.75
min-sinthat 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
(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
groupwhose 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) () >