begin
and functions
with a variable number of argumentsbegin
function 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
ave
should 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
power
function. 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))))))
define
s inside a lambda, a let or a
begin:
If you really just can't hack let
expressions, 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
lambda
's and 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
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) 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
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.
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
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
(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
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) () >