References
Related links
|
Mini-scheme interpreter |
(if A B C)
(if TEST THEN ELSE)where
TEST
is an expression which returns a true or
false value (like a test (< x 1)
or (equal? A "Hello")
).
For example,
>> (if (< 1 2) 7 12) 7 >> (if (< 10 2) 7 12) 12 >> (if (equal? "hi" "Hi") "apple" "banana") banana >> (if (equal? "hi" "hi") "apple" "banana") apple >> (if (equal? "hi" 'hi) "apple" "banana") banana
(define win (window "choice demo")) (define CHaction (action (lambda (e) (if (equal? (readstring CH) "Hello") (writeexpr T "English")) (if (equal? (readstring CH) "Bon Jour") (writeexpr T "French")) (if (equal? (readstring CH) "Gutten Tag") (writeexpr T "German")) (if (equal? (readstring CH) "Konichi-wa") (writeexpr T "Japanese")) (if (equal? (readstring CH) "Hola") (writeexpr T "Spanish")) ))) (define CH (choice "Hello" "Bon Jour" "Gutten Tag" "Konichi-wa" "Hola" CHaction)) (define T (textfield "" 20)) (.add win (col (label "Choice demo") CH T)) (.pack win) (.show win)
The program needs to use readString
because (read CH)
will try
to read the string as a scheme term. So for Bon Jour, it will just read the Bon, and
for Gutten Tag it will just read the Gutten.
f
whose definition contains
another call to f
, this is called textual recursion.
(define win (window "Recursion Demo" (CourierBold 40))) ;; create a window (.resize win 200 200) ;; resize it (define T (textfield "" 10)) ;; create a textfield (.add win T) ;; add it to the window (.pack win) ;; pack the window (.show win) ;; make it appear on the screen (define (f x) ;; add the definition of f to the environment (Thread.sleep 1000L) (writeexpr T x) (if (< x 1) "done" (f (- x 1)))) (f 3) ;; start evolving the expression (f 3)Lets now trace through evolution of this program
(f 3)
==> (define win (window "Recursion Demo" (CourierBold 40))) create a window ==> (.resize win 200 200) resize it ==> (define T (textfield "" 10)) create a textfield ==> (.add win T) add it to the window ==> (.pack win) pack the window ==> (.show win) make it appear on the screen ==> (define (f x) (Thread.sleep 1000L) (writeexpr T x) (if (< x 1) "done" (f (- x 1)))) define the procedure f ==> (f 3) start evaluating (f 3) ==> (Thread.sleep 1000L) (writeexpr T 3) (if (< 3 1) "done" (f (- 3 1))) here it sleeps for 1 second ==> (writeexpr T 3) (if (< 3 1) "done" (f (- 3 1))) here it writes 3 on the textfield T ==> (if (< 3 1) "done" (f (- 3 1))) ==> (if #f "done" (f (- 3 1)) ==> (f (- 3 1)) ==> (f 2) ==> (Thread.sleep 1000L) (writeexpr T 2) (if (< 2 1) "done" (f (- 2 1))) sleep for a second ==> (writeexpr T 2) (if (< 2 1) "done" (f (- 2 1))) write 2 on textfield T ==> (if (< 2 1) "done" (f (- 2 1))) ==> (if #f "done" (f (- 2 1)) ==> (f (- 2 1)) ==> (f 1) ==> (Thread.sleep 1000L) (writeexpr T 1) (if (< 1 1) "done" (f (- 1 1))) sleep for 1 second ==> (writeexpr T 1) (if (< 1 1) "done" (f (- 1 1))) write 1 on textfield T ==> (if (< 1 1) "done" (f (- 1 1))) ==> (if #f "done" (f (- 1 1)) ==> (f (- 1 1)) ==> (f 0) ==> (Thread.sleep 1000L) (writeexpr T 0) (if (< 0 1) "done" (f (- 0 1))) sleep for 1 second ==> (writeexpr T 0) (if (< 0 1) "done" (f (- 0 1))) write 0 on textfield T ==> (if (< 0 1) "done" (f (- 0 1))) ==> (if #t "done" (f (- 0 1)) ==> "done"
Since the intermediate results are shown in the textfield the user can
watch the sum accumulate.
The point here is that M and E are typically very large numbers, possibly hundreds of decimal digits long,
and the simple
The Substitution Model
Up to this point we have primarily focussed our attention on "Reactive Programs"
in which the computer is programmed to respond to user input by carrying out a few commands specified
by the author of the program. For these programs, the computer responds to user inputs almost immediately
(after the system has been completely downloaded into the browser). Another class of programs is the
recursive programs. These programs differ from reactive programs in that the procedures are allowed
to call themselves. This can sometimes lead to infinite loops if the programmer is not careful, but
it also enables computers to solve much more complex problems and, some believe, possibly even to
faithfully imitate human intelligence itself. To understand this class of programs we need a new
model of computation in which an expression E is evaluated by successively transforming it using
the Scheme evaluation rules into new expressions, until we reach a final expression which can not
be evaluate any further (e.g., a number or a string, or a list). This model is called the Substition
model because user-defined procedure calls are handled using substition as described below.
The Substition Rule
This additional power comes from a very simple rule, called the Substitution rule, for evaluating
Scheme programs. The rule states that you evaluate a procedure call
(F a1 a2 ... an)
to a user defined procedure F
with
a definition
(define (F x1 ... xn)
BODY)
by replacing the call with the
BODY
,
but where the variables
x1, ..., xn
have been replaced with
the arguments
a1,...,an
to the call.
Some examples will make this clear.
Countdown: the simplest recursive procedure
The simplest recursive procedure is the countdown procedure
which simply counts from its argument N down to zero.
(define (countdown n)
(if (= n 0) 'done (countdown (- n 1))))
Tracing through the evaluation of the expression (countdown 3)
we see that the call (countdown 3)
gets transformed into
the calls (countdown 2)
,
(countdown 1)
, and finally
(countdown 0)
, which is transformed to the quoted
symbol 'done
, and the evaluation stops as quoted symbols
can not be further evaluated.
(countdown 3)
(if (= 3 0) 'done (countdown (- 3 1)))
(if false 'done (countdown (- 3 1)))
(countdown (- 3 1))
(countdown 2)
(if (= 2 0) 'done (countdown (- 2 1)))
(if false 'done (countdown (- 2 1)))
(countdown (- 2 1))
(countdown 1)
(if (= 1 0) 'done (countdown (- 1 1)))
(if false 'done (countdown (- 1 1)))
(countdown (- 1 1))
(countdown 0)
(if (= 0 0) 'done (countdown (- 0 1)))
'done
Harnessing Countdown
The countdown procedure can be harnassed to perform some useful work
by adding additional expressions before the "if". These will be evaluated
each time through the loop. Thus, to create a "timer applet" which
shows the the seconds countdown in a textfield T, we could use the following
variant of the countdown procedure:
(define T (textfield "" 20 (HelveticaBold 24)))
(define win (window "countdown" (col T)))
(.pack win) (.show win)
(define (sleep N) (Thread.sleep (.longValue (Math.round (* 1000.0 N)))))
(define (visualcountdown n)
(writeexpr T n)
(sleep 0.33)
(if (= n 0) 'done (visualcountdown (- n 1))))
(visualcountdown 10)
You should try running this applet in the Scheme interpreter below:
When we trace this new procedure the body contains three items which
are grouped together and we must evaluate them from left to right,
returning the result of the last expression. We have indicated this by enclosing
the body in an expression of the form
(begin E1 E2 ... En)
Using this convention we can now trace the visual counter program.
(visualcounter 3)
(begin (writeexpr T 3) (sleep 1000) (if (= 3 0) 'done (visualcounter (- 3 1))))
(begin (sleep 1000) (if (= 3 0) 'done (visualcounter (- 3 1))))
(begin (if (= 3 0) 'done (visualcounter (- 3 1))))
(if (= 3 0) 'done (visualcounter (- 3 1)))
(if false 'done (visualcounter (- 3 1)))
(visualcounter (- 3 1))
(visualcounter 2)
(begin (writeexpr T 2) (sleep 1000) (if (= 2 0) 'done (visualcounter (- 2 1))))
(begin (sleep 1000) (if (= 2 0) 'done (visualcounter (- 2 1))))
(begin (if (= 2 0) 'done (visualcounter (- 2 1))))
(if (= 2 0) 'done (visualcounter (- 2 1)))
(if false 'done (visualcounter (- 2 1))))
(visualcounter (- 2 1))
(visualcounter 1)
(begin (writeexpr T 1) (sleep 1000) (if (= 1 0) 'done (visualcounter (- 1 1))))
(begin (sleep 1000) (if (= 1 0) 'done (visualcounter (- 1 1))))
(begin (if (= 1 0) 'done (visualcounter (- 1 1))))
(if (= 1 0) 'done (visualcounter (- 1 1)))
(if false 'done (visualcounter (- 1 1)))
(visualcounter (- 1 1))
(visualcounter 0)
(begin (writeexpr T 0) (sleep 1000) (if (= 0 0) 'done (visualcounter (- 0 1))))
(begin (sleep 1000) (if (= 0 0) 'done (visualcounter (- 0 1))))
(begin (if (= 0 0) 'done (visualcounter (- 0 1))))
(if (= 0 0) 'done (visualcounter (- 0 1)))
'done
The counter procedure can also be modified to create a turtle that takes N random steps
on a graphical canvase.
Summing a sequence of numbers using a modified Countdown
A more interesting application is to find the sum of the integers
from N down to 1. This can be done with following program:
(define TA (textarea 10 20 (HelveticaBold 24)))
(define win (window "countdown" (col TA)))
(.pack win) (.show win)
(define (sleep N) (Thread.sleep (.longValue (Math.round (* 1000.0 N)))))
(define (sumton n s)
(writeexpr TA (string-append (readstring TA) "\n" (list 'sumton n s)))
(sleep 1.0)
(if (= n 0) 'done (sumton (- n 1) (+ s n))))
(sumton 10 0)
Each time through the countdown loop, this procedure does the following:
The textfield T is a new parameter of this procedure so we can call sumton
on different textfields.
Summing a sequence of numbers behind the scenes
If we only wanted the final result and were
not interested in seeing the intermediate results, we could do away with
the textfield and store the intermediate value as a parameter of the
procedure, which we have renamed p
.
(define (p n Subtotal)
(if (= n 0)
Subtotal
(p (- n 1) (+ n Subtotal))))
;; make the GUI
(define X (textfield "100" 10))
(define T (label "0" 10))
(define B (button "sum numbers from 1 to n"
(action (lambda(e)
(writeexpr T "")
(writeexpr T
(p (readexpr X) 0))))))
(define win (window "test" (HelveticaPlain 18)
(tablebyrow 2 (label "n") X
B (label "")
(label "sum:") T)))
(.pack win) (.show win)
It is interesting to trace through this new procedure for a simple call:
(p 5 0)
(if (= 5 0) 0 (p (- 5 1) (+ 5 0)))
(if false 0 (p (- 5 1) (+ 5 0)))
(p (- 5 1) (+ 5 0))
(p 4 5)
(if (= 4 0) 5 (p (- 4 1) (+ 4 5)))
(if false 5 (p (- 4 1) (+ 4 5)))
(p (- 4 1) (+ 4 5))
(p 3 9)
(if (= 3 0) 9 (p (- 3 1) (+ 3 9)))
(if false 9 (p (- 3 1) (+ 3 9)))
(p (- 3 1) (+ 3 9))
(p 2 12)
(if (= 2 0) 12 (p (- 2 1) (+ 2 12)))
(if false 12 (p (- 2 1) (+ 2 12)))
(p (- 2 1) (+ 2 12))
(p 1 14)
(if (= 1 0) 14 (p (- 1 1) (+ 1 14)))
(if false 14 (p (- 1 1) (+ 1 14)))
(p (- 1 1) (+ 1 14))
(p 0 15)
(if (= 0 0) 15 (p (- 0 1) (+ 0 15)))
(if true 15 (p (- 0 1) (+ 0 15)))
15
Tracing a procedure call at a higher level
The pattern we see is that (p a b)
is transformed into
(p (- a 1) (+ a b))
provided a
is not equal to zero.
When a=0
, then it is transformed into b
, which
is the final result. Thus, we can trace through the call (p 10 0)
by doing the indented "if" evaluation steps in our head, and we get:
(p 10 0)
(p 9 10)
(p 8 19)
(p 7 27)
(p 6 34)
(p 5 40)
(p 4 45)
(p 3 49)
(p 2 52)
(p 1 54)
(p 0 55)
55
and indeed, 55 = 10+9+8+7+6+5+4+3+2+1
. Thus a call of
(writeexpr T (p (readexpr X) 0))
would sum the numbers from 1 to whatever is in X and write the answer on T.
Simple Exponentiation
Next we consider a slightly more complicated example, raising a number
to a power.
(define (power x n)
(if (= n 0) 1 (* x (power x (- n 1)))))
As we can see in the trace below (where we have performed the
evaluation of the "if" expression in our heads"), each successive
call creates a larger expression to evaluate until n = 0. After this
point, we have a large expression which is evaluated from the outside
in. It is not hard to see that this method will require about 2n steps
to raise a number x to the power n.
(power 2 6)
(* 2 (power 2 5))
(* 2 (* 2 (power 2 4)))
(* 2 (* 2 (* 2 (power 2 3))))
(* 2 (* 2 (* 2 (* 2 (power 2 2)))))
(* 2 (* 2 (* 2 (* 2 (* 2 (power 2 1))))))
(* 2 (* 2 (* 2 (* 2 (* 2 (* 2 (power 2 0)))))))
(* 2 (* 2 (* 2 (* 2 (* 2 (* 2 1 ))))))
(* 2 (* 2 (* 2 (* 2 (* 2 2 )))))
(* 2 (* 2 (* 2 (* 2 4 ))))
(* 2 (* 2 (* 2 8 )))
(* 2 (* 2 16 ))
(* 2 32 )
64
Clever Exponentiation
Next we show a more clever implementation of exponentiation which
is based on the following two rules of exponents:
xN = (xN/2) 2 (N even)
xN = x* (x(N-1)/2)2 (N odd)
The code for this procedure is as follows:
(define (sq x) (* x x))
(define (expt x n)
(if (= n 0)
1.0
(if (even? n)
(sq (expt x (/ n 2)))
(* x (sq (expt x (/ (- n 1) 2)))))))
Tracing through this procedure for the case of raising 2 to the 6th
we don't see much advantage. Indeed, it seems significantly more
complex.
(expt 2 6)
(sq (expt 2 3))
(sq (* 2 (sq (expt 2 1))))
(sq (* 2 (sq (* 2 (expt 2 0)))))
(sq (* 2 (sq (* 2 1.0))))
(sq (* 2 (sq 2.0)))
(sq (* 2 4.0))
(sq 8.0)
64.0
But the benefit of this approach comes when we try to raise 2 to the 33th
power:
(expt 2 33)
(* 2 (expt 2 32))
(* 2 (sq (expt 2 16)))
(* 2 (sq (sq (expt 2 8))))
(* 2 (sq (sq (sq (expt 2 4)))))
(* 2 (sq (sq (sq (sq (expt 2 2))))))
(* 2 (sq (sq (sq (sq (sq (expt 2 1)))))))
(* 2 (sq (sq (sq (sq (sq (* 2 (expt 2 0))))))))
(* 2 (sq (sq (sq (sq (sq (* 2 1.0)))))))
(* 2 (sq (sq (sq (sq (sq 2.0))))))
(* 2 (sq (sq (sq (sq 4.0)))))
(* 2 (sq (sq (sq 16.0))))
(* 2 (sq (sq 256.0)))
(* 2 (sq 65536.0))
(* 2 4294967296.0)
8589934592.0
This procedure accomplishes in 14 steps what would require
66 steps for the power
procedure. Observe that
for each successive call of the expt procedure the argument
is halved. Indeed, the number of steps required by
the (expt x n) procedure is exactly twice the length
of the number n when written as a binary number.
More Advanced Topics
Below we include some more advanced topics for those
who want to explore these concepts a little further.
Clever Exponentiation and the RSA public key algorithm
This clever exponentiation idea is the most important procedure in
the RSA public key algorithm. If you recall, the way to encode a
secret message S with a key (E M) is to translate S to a number
N (or maybe a sequence of numbers N1, N2, ... if S is a long message),
and then to compute the encoded message by raising N to the Eth power,
dividing by M, and taking the remainder. In Scheme, (modulo A M) is the
function that takes the remainder of A divided by M, thus the RSA
encoding of S is a number T computed by
T = (modulo NE M)
Similarly, we decode T to get back our original message N by raising it the E' power and taking the remainder
upon division by M:
N = (modulo TE' M)
where (E',M) is the private key.
power
procedure would run for billions of years without finishing. However,
the clever exponentiation algorithm can be modified to compute (modulo NE M)
in at most
2K steps where K is the lenght of N as a binary number, typically around 1000. The modified version
is called exptmod and is written below:
(import "java.math.BigInteger") ;; need to use BigInteger's
(define (exptmoddemo x n m)
(define (sq x) (.multiply x x))
(define zero (BigInteger. "0"))
(define one (BigInteger. "1"))
(define two (BigInteger. "2"))
(define (even? n) (= (.mod n two) 0))
(define (exptmod x n m)
(.mod
(if (.equals n zero)
one
(if (even? n)
(sq (exptmod x (.divide n two) m))
(.multiply x (sq (exptmod x (.divide (.subtract n one) two) m)))))
m))
(exptmod (BigInteger. x) (BigInteger. n) (BigInteger. m))
)
(exptmoddemo "12321" "1234567890123456789901234567" "9876543210987654321")
Here we use mult and div instead of * and /, because we need to use special
procedures (available in Java) for computing with very large numbers.
As a final example, we show how to compute the remainder of (3 raised to the 33 power)
when divided by 100. (Note: to save space we have abbreviated modulo
to m
in the following trace.)
(exptmod 3 17 100)
(m (* 3 (sq (exptmod 3 8)))) 100)
(m (* 3 (sq (m (sq (exptmod 3 4))) 100))) 100)
(m (* 3 (sq (m (sq (m (sq (exptmod 3 2)) 100)) 100))) 100)
(m (* 3 (sq (m (sq (m (sq (m (sq (exptmod 3 1)) 100)) 100)) 100))) 100)
(m (* 3 (sq (m (sq (m (sq (m (sq (m (* 3 (sq (exptmod 3 0))) 100 )) 100)) 100)) 100))) 100)
(m (* 3 (sq (m (sq (m (sq (m (sq (m (* 3 (sq 1 )) 100 )) 100)) 100)) 100))) 100)
(m (* 3 (sq (m (sq (m (sq (m (sq 3 ) 100)) 100)) 100))) 100)
(m (* 3 (sq (m (sq (m (sq 9 ) 100)) 100))) 100)
(m (* 3 (sq (m (sq 81 ) 100))) 100)
(m (* 3 (sq (m 6461 100))) 100)
(m (* 3 (sq 61 ) 100)
(m 11163 100)
63
The basic algorithm is to either square or square and multiply by 3 and then to take the remainder. This is iterate
once for each binary digit of the exponent E.